1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
13:
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
28: class Horde_Date_Recurrence
29: {
30:
31: const RECUR_NONE = 0;
32:
33:
34: const RECUR_DAILY = 1;
35:
36:
37: const RECUR_WEEKLY = 2;
38:
39:
40: const RECUR_MONTHLY_DATE = 3;
41:
42:
43: const RECUR_MONTHLY_WEEKDAY = 4;
44:
45:
46: const RECUR_YEARLY_DATE = 5;
47:
48:
49: const RECUR_YEARLY_DAY = 6;
50:
51:
52: const RECUR_YEARLY_WEEKDAY = 7;
53:
54: 55: 56: 57: 58:
59: public $start;
60:
61: 62: 63: 64: 65:
66: public $recurEnd = null;
67:
68: 69: 70: 71: 72:
73: public $recurCount = null;
74:
75: 76: 77: 78: 79:
80: public $recurType = self::RECUR_NONE;
81:
82: 83: 84: 85: 86: 87:
88: public $recurInterval = 1;
89:
90: 91: 92: 93: 94:
95: public $recurData = null;
96:
97: 98: 99: 100: 101:
102: public $exceptions = array();
103:
104: 105: 106: 107: 108:
109: public $completions = array();
110:
111: 112: 113: 114: 115:
116: public function __construct($start)
117: {
118: $this->start = new Horde_Date($start);
119: }
120:
121: 122: 123:
124: public function reset()
125: {
126: $this->recurEnd = null;
127: $this->recurCount = null;
128: $this->recurType = self::RECUR_NONE;
129: $this->recurInterval = 1;
130: $this->recurData = null;
131: $this->exceptions = array();
132: $this->completions = array();
133: }
134:
135: 136: 137: 138: 139: 140: 141: 142:
143: public function recurOnDay($dayMask)
144: {
145: return ($this->recurData & $dayMask);
146: }
147:
148: 149: 150: 151: 152: 153:
154: public function setRecurOnDay($dayMask)
155: {
156: $this->recurData = $dayMask;
157: }
158:
159: 160: 161: 162: 163: 164:
165: public function getRecurOnDays()
166: {
167: return $this->recurData;
168: }
169:
170: 171: 172: 173: 174: 175: 176: 177:
178: public function hasRecurType($recurrence)
179: {
180: return ($recurrence == $this->recurType);
181: }
182:
183: 184: 185: 186: 187:
188: public function setRecurType($recurrence)
189: {
190: $this->recurType = $recurrence;
191: }
192:
193: 194: 195: 196: 197:
198: public function getRecurType()
199: {
200: return $this->recurType;
201: }
202:
203: 204: 205: 206: 207:
208: public function getRecurName()
209: {
210: switch ($this->getRecurType()) {
211: case self::RECUR_NONE: return Horde_Date_Translation::t("No recurrence");
212: case self::RECUR_DAILY: return Horde_Date_Translation::t("Daily");
213: case self::RECUR_WEEKLY: return Horde_Date_Translation::t("Weekly");
214: case self::RECUR_MONTHLY_DATE:
215: case self::RECUR_MONTHLY_WEEKDAY: return Horde_Date_Translation::t("Monthly");
216: case self::RECUR_YEARLY_DATE:
217: case self::RECUR_YEARLY_DAY:
218: case self::RECUR_YEARLY_WEEKDAY: return Horde_Date_Translation::t("Yearly");
219: }
220: }
221:
222: 223: 224: 225: 226:
227: public function setRecurInterval($interval)
228: {
229: if ($interval > 0) {
230: $this->recurInterval = $interval;
231: }
232: }
233:
234: 235: 236: 237: 238:
239: public function getRecurInterval()
240: {
241: return $this->recurInterval;
242: }
243:
244: 245: 246: 247: 248:
249: public function setRecurCount($count)
250: {
251: if ($count > 0) {
252: $this->recurCount = (int)$count;
253:
254: $this->recurEnd = null;
255: } else {
256: $this->recurCount = null;
257: }
258: }
259:
260: 261: 262: 263: 264:
265: public function getRecurCount()
266: {
267: return $this->recurCount;
268: }
269:
270: 271: 272: 273: 274:
275: public function hasRecurCount()
276: {
277: return isset($this->recurCount);
278: }
279:
280: 281: 282: 283: 284:
285: public function setRecurStart($start)
286: {
287: $this->start = clone $start;
288: }
289:
290: 291: 292: 293: 294:
295: public function getRecurStart()
296: {
297: return $this->start;
298: }
299:
300: 301: 302: 303: 304:
305: public function setRecurEnd($end)
306: {
307: if (!empty($end)) {
308:
309: $this->recurCount = null;
310: $this->recurEnd = clone $end;
311: } else {
312: $this->recurEnd = $end;
313: }
314: }
315:
316: 317: 318: 319: 320:
321: public function getRecurEnd()
322: {
323: return $this->recurEnd;
324: }
325:
326: 327: 328: 329: 330:
331: public function hasRecurEnd()
332: {
333: return isset($this->recurEnd) && isset($this->recurEnd->year) &&
334: $this->recurEnd->year != 9999;
335: }
336:
337: 338: 339: 340: 341: 342: 343: 344: 345:
346: public function nextRecurrence($after)
347: {
348: if (!($after instanceof Horde_Date)) {
349: $after = new Horde_Date($after);
350: } else {
351: $after = clone($after);
352: }
353:
354:
355: $after->setTimezone($this->start->timezone);
356: if ($this->start->compareDateTime($after) >= 0) {
357: return clone $this->start;
358: }
359:
360: if ($this->recurInterval == 0) {
361: return false;
362: }
363:
364: switch ($this->getRecurType()) {
365: case self::RECUR_DAILY:
366: $diff = $this->start->diff($after);
367: $recur = ceil($diff / $this->recurInterval);
368: if ($this->recurCount && $recur >= $this->recurCount) {
369: return false;
370: }
371:
372: $recur *= $this->recurInterval;
373: $next = $this->start->add(array('day' => $recur));
374: if ((!$this->hasRecurEnd() ||
375: $next->compareDateTime($this->recurEnd) <= 0) &&
376: $next->compareDateTime($after) >= 0) {
377: return $next;
378: }
379: break;
380:
381: case self::RECUR_WEEKLY:
382: if (empty($this->recurData)) {
383: return false;
384: }
385:
386: $start_week = Horde_Date_Utils::firstDayOfWeek($this->start->format('W'),
387: $this->start->year);
388: $start_week->timezone = $this->start->timezone;
389: $start_week->hour = $this->start->hour;
390: $start_week->min = $this->start->min;
391: $start_week->sec = $this->start->sec;
392:
393:
394:
395:
396: $week = $after->format('W');
397: if ($week == 1 && $after->month == 12) {
398: $theYear = $after->year + 1;
399: } elseif ($week >= 52 && $after->month == 1) {
400: $theYear = $after->year - 1;
401: } else {
402: $theYear = $after->year;
403: }
404:
405: $after_week = Horde_Date_Utils::firstDayOfWeek($week, $theYear);
406: $after_week->timezone = $this->start->timezone;
407: $after_week_end = clone $after_week;
408: $after_week_end->mday += 7;
409:
410: $diff = $start_week->diff($after_week);
411: $interval = $this->recurInterval * 7;
412: $repeats = floor($diff / $interval);
413: if ($diff % $interval < 7) {
414: $recur = $diff;
415: } else {
416: 417: 418: 419: 420: 421: 422: 423:
424: $recur = $interval * ($repeats + 1);
425: }
426:
427: if ($this->hasRecurCount()) {
428: $recurrences = 0;
429: 430: 431: 432: 433:
434: $next = clone $start_week;
435: while ($next->compareDateTime($this->start) < 0) {
436: if ($this->recurOnDay((int)pow(2, $next->dayOfWeek()))) {
437: $recurrences--;
438: }
439: ++$next->mday;
440: }
441: if ($repeats > 0) {
442: $weekdays = $this->recurData;
443: $total_recurrences_per_week = 0;
444: while ($weekdays > 0) {
445: if ($weekdays % 2) {
446: $total_recurrences_per_week++;
447: }
448: $weekdays = ($weekdays - ($weekdays % 2)) / 2;
449: }
450: $recurrences += $total_recurrences_per_week * $repeats;
451: }
452: }
453:
454: $next = clone $start_week;
455: $next->mday += $recur;
456: while ($next->compareDateTime($after) < 0 &&
457: $next->compareDateTime($after_week_end) < 0) {
458: if ($this->hasRecurCount()
459: && $next->compareDateTime($after) < 0
460: && $this->recurOnDay((int)pow(2, $next->dayOfWeek()))) {
461: $recurrences++;
462: }
463: ++$next->mday;
464: }
465: if ($this->hasRecurCount() &&
466: $recurrences >= $this->recurCount) {
467: return false;
468: }
469: if (!$this->hasRecurEnd() ||
470: $next->compareDateTime($this->recurEnd) <= 0) {
471: if ($next->compareDateTime($after_week_end) >= 0) {
472: return $this->nextRecurrence($after_week_end);
473: }
474: while (!$this->recurOnDay((int)pow(2, $next->dayOfWeek())) &&
475: $next->compareDateTime($after_week_end) < 0) {
476: ++$next->mday;
477: }
478: if (!$this->hasRecurEnd() ||
479: $next->compareDateTime($this->recurEnd) <= 0) {
480: if ($next->compareDateTime($after_week_end) >= 0) {
481: return $this->nextRecurrence($after_week_end);
482: } else {
483: return $next;
484: }
485: }
486: }
487: break;
488:
489: case self::RECUR_MONTHLY_DATE:
490: $start = clone $this->start;
491: if ($after->compareDateTime($start) < 0) {
492: $after = clone $start;
493: } else {
494: $after = clone $after;
495: }
496:
497:
498:
499: if ($after->mday > $start->mday) {
500: ++$after->month;
501: $after->mday = $start->mday;
502: }
503:
504:
505: $offset = ($after->month - $start->month) + ($after->year - $start->year) * 12;
506: $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
507:
508: if ($this->recurCount &&
509: ($offset / $this->recurInterval) >= $this->recurCount) {
510: return false;
511: }
512: $start->month += $offset;
513: $count = $offset / $this->recurInterval;
514:
515: do {
516: if ($this->recurCount &&
517: $count++ >= $this->recurCount) {
518: return false;
519: }
520:
521:
522: if ($this->hasRecurEnd() &&
523: $this->recurEnd->compareDateTime($start) < 0) {
524: return false;
525: }
526: if ($start->isValid()) {
527: return $start;
528: }
529:
530:
531:
532:
533:
534: if ($this->recurInterval == 12 && ($start->month != 2 || $start->mday > 29)) {
535: return false;
536: }
537:
538:
539: $start->month += $this->recurInterval;
540: } while (true);
541:
542: break;
543:
544: case self::RECUR_MONTHLY_WEEKDAY:
545:
546: $estart = clone $this->start;
547:
548:
549: $nth = ceil($this->start->mday / 7);
550: $weekday = $estart->dayOfWeek();
551:
552:
553: $offset = ($after->month - $estart->month) + ($after->year - $estart->year) * 12;
554: $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
555:
556:
557: $estart->month += $offset - $this->recurInterval;
558:
559: $count = $offset / $this->recurInterval;
560: do {
561: if ($this->recurCount &&
562: $count++ >= $this->recurCount) {
563: return false;
564: }
565:
566: $estart->month += $this->recurInterval;
567:
568: $next = clone $estart;
569: $next->setNthWeekday($weekday, $nth);
570:
571: if ($next->compareDateTime($after) < 0) {
572:
573: continue;
574: }
575: if ($this->hasRecurEnd() &&
576: $next->compareDateTime($this->recurEnd) > 0) {
577:
578:
579: return false;
580: }
581:
582:
583: break;
584: } while (true);
585:
586: return $next;
587:
588: case self::RECUR_YEARLY_DATE:
589:
590: $estart = clone $this->start;
591: $after = clone $after;
592:
593: if ($after->month > $estart->month ||
594: ($after->month == $estart->month && $after->mday > $estart->mday)) {
595: ++$after->year;
596: $after->month = $estart->month;
597: $after->mday = $estart->mday;
598: }
599:
600:
601: if ($estart->month == 2 && $estart->mday == 29) {
602: while (!Horde_Date_Utils::isLeapYear($after->year)) {
603: ++$after->year;
604: }
605: }
606:
607:
608: $offset = $after->year - $estart->year;
609: if ($offset > 0) {
610: $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
611: $estart->year += $offset;
612: }
613:
614:
615: if ($this->recurCount &&
616: $offset >= $this->recurCount) {
617: return false;
618: }
619: if ($this->hasRecurEnd() &&
620: $this->recurEnd->compareDateTime($estart) < 0) {
621: return false;
622: }
623:
624: return $estart;
625:
626: case self::RECUR_YEARLY_DAY:
627:
628: $dayofyear = $this->start->dayOfYear();
629: $count = ($after->year - $this->start->year) / $this->recurInterval + 1;
630: if ($this->recurCount &&
631: ($count > $this->recurCount ||
632: ($count == $this->recurCount &&
633: $after->dayOfYear() > $dayofyear))) {
634: return false;
635: }
636:
637:
638: $estart = clone $this->start;
639: $estart->year += floor($count - 1) * $this->recurInterval;
640:
641:
642: $estart->mday += $dayofyear - $estart->dayOfYear();
643:
644:
645: if ($estart->compareDate($after) < 0) {
646: $estart->year += $this->recurInterval;
647: $estart->mday += $dayofyear - $estart->dayOfYear();
648: }
649:
650:
651: if ($this->hasRecurEnd() &&
652: $this->recurEnd->compareDateTime($estart) < 0) {
653: return false;
654: }
655:
656: return $estart;
657:
658: case self::RECUR_YEARLY_WEEKDAY:
659:
660: $estart = clone $this->start;
661:
662:
663: $nth = ceil($this->start->mday / 7);
664: $weekday = $estart->dayOfWeek();
665:
666:
667: $offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
668:
669:
670: $estart->year += $offset - $this->recurInterval;
671:
672: $count = $offset / $this->recurInterval;
673: do {
674: if ($this->recurCount &&
675: $count++ >= $this->recurCount) {
676: return false;
677: }
678:
679: $estart->year += $this->recurInterval;
680:
681: $next = clone $estart;
682: $next->setNthWeekday($weekday, $nth);
683:
684: if ($next->compareDateTime($after) < 0) {
685:
686: continue;
687: }
688: if ($this->hasRecurEnd() &&
689: $next->compareDateTime($this->recurEnd) > 0) {
690:
691:
692: return false;
693: }
694:
695:
696: break;
697: } while (true);
698:
699: return $next;
700: }
701:
702:
703:
704: return false;
705: }
706:
707: 708: 709: 710: 711: 712:
713: public function hasActiveRecurrence()
714: {
715: if (!$this->hasRecurEnd()) {
716: return true;
717: }
718:
719: $next = $this->nextRecurrence(new Horde_Date($this->start));
720: while (is_object($next)) {
721: if (!$this->hasException($next->year, $next->month, $next->mday) &&
722: !$this->hasCompletion($next->year, $next->month, $next->mday)) {
723: return true;
724: }
725:
726: $next = $this->nextRecurrence($next->add(array('day' => 1)));
727: }
728:
729: return false;
730: }
731:
732: 733: 734: 735: 736: 737: 738: 739: 740: 741:
742: public function nextActiveRecurrence($afterDate)
743: {
744: $next = $this->nextRecurrence($afterDate);
745: while (is_object($next)) {
746: if (!$this->hasException($next->year, $next->month, $next->mday) &&
747: !$this->hasCompletion($next->year, $next->month, $next->mday)) {
748: return $next;
749: }
750: $next->mday++;
751: $next = $this->nextRecurrence($next);
752: }
753:
754: return false;
755: }
756:
757: 758: 759: 760: 761: 762: 763:
764: public function addException($year, $month, $mday)
765: {
766: $this->exceptions[] = sprintf('%04d%02d%02d', $year, $month, $mday);
767: }
768:
769: 770: 771: 772: 773: 774: 775:
776: public function deleteException($year, $month, $mday)
777: {
778: $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->exceptions);
779: if ($key !== false) {
780: unset($this->exceptions[$key]);
781: }
782: }
783:
784: 785: 786: 787: 788: 789: 790: 791: 792:
793: public function hasException($year, $month, $mday)
794: {
795: return in_array(sprintf('%04d%02d%02d', $year, $month, $mday),
796: $this->getExceptions());
797: }
798:
799: 800: 801: 802: 803: 804:
805: public function getExceptions()
806: {
807: return $this->exceptions;
808: }
809:
810: 811: 812: 813: 814: 815: 816:
817: public function addCompletion($year, $month, $mday)
818: {
819: $this->completions[] = sprintf('%04d%02d%02d', $year, $month, $mday);
820: }
821:
822: 823: 824: 825: 826: 827: 828:
829: public function deleteCompletion($year, $month, $mday)
830: {
831: $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->completions);
832: if ($key !== false) {
833: unset($this->completions[$key]);
834: }
835: }
836:
837: 838: 839: 840: 841: 842: 843: 844: 845:
846: public function hasCompletion($year, $month, $mday)
847: {
848: return in_array(sprintf('%04d%02d%02d', $year, $month, $mday),
849: $this->getCompletions());
850: }
851:
852: 853: 854: 855: 856: 857:
858: public function getCompletions()
859: {
860: return $this->completions;
861: }
862:
863: 864: 865: 866: 867: 868: 869: 870:
871: public function fromRRule10($rrule)
872: {
873: $this->reset();
874:
875: if (!$rrule) {
876: return;
877: }
878:
879: if (!preg_match('/([A-Z]+)(\d+)?(.*)/', $rrule, $matches)) {
880:
881: $this->setRecurType(self::RECUR_NONE);
882: }
883:
884:
885: $this->setRecurInterval(!empty($matches[2]) ? $matches[2] : 1);
886:
887: $remainder = trim($matches[3]);
888:
889: switch ($matches[1]) {
890: case 'D':
891: $this->setRecurType(self::RECUR_DAILY);
892: break;
893:
894: case 'W':
895: $this->setRecurType(self::RECUR_WEEKLY);
896: if (!empty($remainder)) {
897: $maskdays = array(
898: 'SU' => Horde_Date::MASK_SUNDAY,
899: 'MO' => Horde_Date::MASK_MONDAY,
900: 'TU' => Horde_Date::MASK_TUESDAY,
901: 'WE' => Horde_Date::MASK_WEDNESDAY,
902: 'TH' => Horde_Date::MASK_THURSDAY,
903: 'FR' => Horde_Date::MASK_FRIDAY,
904: 'SA' => Horde_Date::MASK_SATURDAY,
905: );
906: $mask = 0;
907: while (preg_match('/^ ?[A-Z]{2} ?/', $remainder, $matches)) {
908: $day = trim($matches[0]);
909: $remainder = substr($remainder, strlen($matches[0]));
910: $mask |= $maskdays[$day];
911: }
912: $this->setRecurOnDay($mask);
913: } else {
914:
915: $maskdays = array(
916: Horde_Date::DATE_SUNDAY => Horde_Date::MASK_SUNDAY,
917: Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY,
918: Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY,
919: Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY,
920: Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY,
921: Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY,
922: Horde_Date::DATE_SATURDAY => Horde_Date::MASK_SATURDAY,
923: );
924: $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]);
925: }
926: break;
927:
928: case 'MP':
929: $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY);
930: break;
931:
932: case 'MD':
933: $this->setRecurType(self::RECUR_MONTHLY_DATE);
934: break;
935:
936: case 'YM':
937: $this->setRecurType(self::RECUR_YEARLY_DATE);
938: break;
939:
940: case 'YD':
941: $this->setRecurType(self::RECUR_YEARLY_DAY);
942: break;
943: }
944:
945:
946: while ($remainder && !preg_match('/^(#\d+|\d{8})($| |T\d{6})/', $remainder)) {
947: $remainder = substr($remainder, 1);
948: }
949: if (!empty($remainder)) {
950: if (strpos($remainder, '#') === 0) {
951: $this->setRecurCount(substr($remainder, 1));
952: } else {
953: list($year, $month, $mday, $hour, $min, $sec, $tz) =
954: sscanf($remainder, '%04d%02d%02dT%02d%02d%02d%s');
955: $this->setRecurEnd(new Horde_Date(array('year' => $year,
956: 'month' => $month,
957: 'mday' => $mday,
958: 'hour' => $hour,
959: 'min' => $min,
960: 'sec' => $sec),
961: $tz == 'Z' ? 'UTC' : null));
962: }
963: }
964: }
965:
966: 967: 968: 969: 970: 971: 972: 973: 974: 975:
976: public function toRRule10($calendar)
977: {
978: switch ($this->recurType) {
979: case self::RECUR_NONE:
980: return '';
981:
982: case self::RECUR_DAILY:
983: $rrule = 'D' . $this->recurInterval;
984: break;
985:
986: case self::RECUR_WEEKLY:
987: $rrule = 'W' . $this->recurInterval;
988: $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
989:
990: for ($i = 0; $i <= 7; ++$i) {
991: if ($this->recurOnDay(pow(2, $i))) {
992: $rrule .= ' ' . $vcaldays[$i];
993: }
994: }
995: break;
996:
997: case self::RECUR_MONTHLY_DATE:
998: $rrule = 'MD' . $this->recurInterval . ' ' . trim($this->start->mday);
999: break;
1000:
1001: case self::RECUR_MONTHLY_WEEKDAY:
1002: $nth_weekday = (int)($this->start->mday / 7);
1003: if (($this->start->mday % 7) > 0) {
1004: $nth_weekday++;
1005: }
1006:
1007: $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
1008: $rrule = 'MP' . $this->recurInterval . ' ' . $nth_weekday . '+ ' . $vcaldays[$this->start->dayOfWeek()];
1009:
1010: break;
1011:
1012: case self::RECUR_YEARLY_DATE:
1013: $rrule = 'YM' . $this->recurInterval . ' ' . trim($this->start->month);
1014: break;
1015:
1016: case self::RECUR_YEARLY_DAY:
1017: $rrule = 'YD' . $this->recurInterval . ' ' . $this->start->dayOfYear();
1018: break;
1019:
1020: default:
1021: return '';
1022: }
1023:
1024: if ($this->hasRecurEnd()) {
1025: $recurEnd = clone $this->recurEnd;
1026: return $rrule . ' ' . $calendar->_exportDateTime($recurEnd);
1027: }
1028:
1029: return $rrule . ' #' . (int)$this->getRecurCount();
1030: }
1031:
1032: 1033: 1034: 1035: 1036: 1037: 1038: 1039: 1040:
1041: public function fromRRule20($rrule)
1042: {
1043: $this->reset();
1044:
1045:
1046: $rdata = array();
1047: $parts = explode(';', $rrule);
1048: foreach ($parts as $part) {
1049: list($key, $value) = explode('=', $part, 2);
1050: $rdata[Horde_String::upper($key)] = $value;
1051: }
1052:
1053: if (isset($rdata['FREQ'])) {
1054:
1055: $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1);
1056:
1057: switch (Horde_String::upper($rdata['FREQ'])) {
1058: case 'DAILY':
1059: $this->setRecurType(self::RECUR_DAILY);
1060: break;
1061:
1062: case 'WEEKLY':
1063: $this->setRecurType(self::RECUR_WEEKLY);
1064: if (isset($rdata['BYDAY'])) {
1065: $maskdays = array(
1066: 'SU' => Horde_Date::MASK_SUNDAY,
1067: 'MO' => Horde_Date::MASK_MONDAY,
1068: 'TU' => Horde_Date::MASK_TUESDAY,
1069: 'WE' => Horde_Date::MASK_WEDNESDAY,
1070: 'TH' => Horde_Date::MASK_THURSDAY,
1071: 'FR' => Horde_Date::MASK_FRIDAY,
1072: 'SA' => Horde_Date::MASK_SATURDAY,
1073: );
1074: $days = explode(',', $rdata['BYDAY']);
1075: $mask = 0;
1076: foreach ($days as $day) {
1077: $mask |= $maskdays[$day];
1078: }
1079: $this->setRecurOnDay($mask);
1080: } else {
1081:
1082:
1083: $maskdays = array(
1084: Horde_Date::DATE_SUNDAY => Horde_Date::MASK_SUNDAY,
1085: Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY,
1086: Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY,
1087: Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY,
1088: Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY,
1089: Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY,
1090: Horde_Date::DATE_SATURDAY => Horde_Date::MASK_SATURDAY);
1091: $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]);
1092: }
1093: break;
1094:
1095: case 'MONTHLY':
1096: if (isset($rdata['BYDAY'])) {
1097: $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY);
1098: } else {
1099: $this->setRecurType(self::RECUR_MONTHLY_DATE);
1100: }
1101: break;
1102:
1103: case 'YEARLY':
1104: if (isset($rdata['BYYEARDAY'])) {
1105: $this->setRecurType(self::RECUR_YEARLY_DAY);
1106: } elseif (isset($rdata['BYDAY'])) {
1107: $this->setRecurType(self::RECUR_YEARLY_WEEKDAY);
1108: } else {
1109: $this->setRecurType(self::RECUR_YEARLY_DATE);
1110: }
1111: break;
1112: }
1113:
1114: if (isset($rdata['UNTIL'])) {
1115: list($year, $month, $mday) = sscanf($rdata['UNTIL'],
1116: '%04d%02d%02d');
1117: $this->setRecurEnd(new Horde_Date(array('year' => $year,
1118: 'month' => $month,
1119: 'mday' => $mday)));
1120: }
1121: if (isset($rdata['COUNT'])) {
1122: $this->setRecurCount($rdata['COUNT']);
1123: }
1124: } else {
1125:
1126: $this->setRecurType(self::RECUR_NONE);
1127: }
1128: }
1129:
1130: 1131: 1132: 1133: 1134: 1135: 1136: 1137: 1138: 1139: 1140:
1141: public function toRRule20($calendar)
1142: {
1143: switch ($this->recurType) {
1144: case self::RECUR_NONE:
1145: return '';
1146:
1147: case self::RECUR_DAILY:
1148: $rrule = 'FREQ=DAILY;INTERVAL=' . $this->recurInterval;
1149: break;
1150:
1151: case self::RECUR_WEEKLY:
1152: $rrule = 'FREQ=WEEKLY;INTERVAL=' . $this->recurInterval;
1153: $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
1154:
1155: for ($i = $flag = 0; $i <= 7; ++$i) {
1156: if ($this->recurOnDay(pow(2, $i))) {
1157: if ($flag == 0) {
1158: $rrule .= ';BYDAY=';
1159: $flag = 1;
1160: } else {
1161: $rrule .= ',';
1162: }
1163: $rrule .= $vcaldays[$i];
1164: }
1165: }
1166: break;
1167:
1168: case self::RECUR_MONTHLY_DATE:
1169: $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval;
1170: break;
1171:
1172: case self::RECUR_MONTHLY_WEEKDAY:
1173: $nth_weekday = (int)($this->start->mday / 7);
1174: if (($this->start->mday % 7) > 0) {
1175: $nth_weekday++;
1176: }
1177: $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
1178: $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval
1179: . ';BYDAY=' . $nth_weekday . $vcaldays[$this->start->dayOfWeek()];
1180: break;
1181:
1182: case self::RECUR_YEARLY_DATE:
1183: $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval;
1184: break;
1185:
1186: case self::RECUR_YEARLY_DAY:
1187: $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval
1188: . ';BYYEARDAY=' . $this->start->dayOfYear();
1189: break;
1190:
1191: case self::RECUR_YEARLY_WEEKDAY:
1192: $nth_weekday = (int)($this->start->mday / 7);
1193: if (($this->start->mday % 7) > 0) {
1194: $nth_weekday++;
1195: }
1196: $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
1197: $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval
1198: . ';BYDAY='
1199: . $nth_weekday
1200: . $vcaldays[$this->start->dayOfWeek()]
1201: . ';BYMONTH=' . $this->start->month;
1202: break;
1203: }
1204:
1205: if ($this->hasRecurEnd()) {
1206: $recurEnd = clone $this->recurEnd;
1207: $rrule .= ';UNTIL=' . $calendar->_exportDateTime($recurEnd);
1208: }
1209: if ($count = $this->getRecurCount()) {
1210: $rrule .= ';COUNT=' . $count;
1211: }
1212: return $rrule;
1213: }
1214:
1215: 1216: 1217: 1218: 1219: 1220: 1221:
1222: public function fromHash($hash)
1223: {
1224: $this->reset();
1225:
1226: if (!isset($hash['interval']) || !isset($hash['cycle'])) {
1227: $this->setRecurType(self::RECUR_NONE);
1228: return false;
1229: }
1230:
1231: $this->setRecurInterval((int)$hash['interval']);
1232:
1233: $parse_day = false;
1234: $set_daymask = false;
1235: $update_month = false;
1236: $update_daynumber = false;
1237: $update_weekday = false;
1238: $nth_weekday = -1;
1239:
1240: switch ($hash['cycle']) {
1241: case 'daily':
1242: $this->setRecurType(self::RECUR_DAILY);
1243: break;
1244:
1245: case 'weekly':
1246: $this->setRecurType(self::RECUR_WEEKLY);
1247: $parse_day = true;
1248: $set_daymask = true;
1249: break;
1250:
1251: case 'monthly':
1252: if (!isset($hash['daynumber'])) {
1253: $this->setRecurType(self::RECUR_NONE);
1254: return false;
1255: }
1256:
1257: switch ($hash['type']) {
1258: case 'daynumber':
1259: $this->setRecurType(self::RECUR_MONTHLY_DATE);
1260: $update_daynumber = true;
1261: break;
1262:
1263: case 'weekday':
1264: $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY);
1265: $nth_weekday = (int)$hash['daynumber'];
1266: $hash['daynumber'] = 1;
1267: $parse_day = true;
1268: $update_daynumber = true;
1269: $update_weekday = true;
1270: break;
1271: }
1272: break;
1273:
1274: case 'yearly':
1275: if (!isset($hash['type'])) {
1276: $this->setRecurType(self::RECUR_NONE);
1277: return false;
1278: }
1279:
1280: switch ($hash['type']) {
1281: case 'monthday':
1282: $this->setRecurType(self::RECUR_YEARLY_DATE);
1283: $update_month = true;
1284: $update_daynumber = true;
1285: break;
1286:
1287: case 'yearday':
1288: if (!isset($hash['month'])) {
1289: $this->setRecurType(self::RECUR_NONE);
1290: return false;
1291: }
1292:
1293: $this->setRecurType(self::RECUR_YEARLY_DAY);
1294:
1295: $hash['month'] = 'january';
1296: $update_month = true;
1297: $update_daynumber = true;
1298: break;
1299:
1300: case 'weekday':
1301: if (!isset($hash['daynumber'])) {
1302: $this->setRecurType(self::RECUR_NONE);
1303: return false;
1304: }
1305:
1306: $this->setRecurType(self::RECUR_YEARLY_WEEKDAY);
1307: $nth_weekday = (int)$hash['daynumber'];
1308: $hash['daynumber'] = 1;
1309: $parse_day = true;
1310: $update_month = true;
1311: $update_daynumber = true;
1312: $update_weekday = true;
1313: break;
1314: }
1315: }
1316:
1317: if (isset($hash['range-type']) && isset($hash['range'])) {
1318: switch ($hash['range-type']) {
1319: case 'number':
1320: $this->setRecurCount((int)$hash['range']);
1321: break;
1322:
1323: case 'date':
1324: $recur_end = new Horde_Date($hash['range']);
1325: $recur_end->hour = 23;
1326: $recur_end->min = 59;
1327: $recur_end->sec = 59;
1328: $this->setRecurEnd($recur_end);
1329: break;
1330: }
1331: }
1332:
1333:
1334: $last_found_day = -1;
1335: if ($parse_day) {
1336: if (!isset($hash['day'])) {
1337: $this->setRecurType(self::RECUR_NONE);
1338: return false;
1339: }
1340:
1341: $mask = 0;
1342: $bits = array(
1343: 'monday' => Horde_Date::MASK_MONDAY,
1344: 'tuesday' => Horde_Date::MASK_TUESDAY,
1345: 'wednesday' => Horde_Date::MASK_WEDNESDAY,
1346: 'thursday' => Horde_Date::MASK_THURSDAY,
1347: 'friday' => Horde_Date::MASK_FRIDAY,
1348: 'saturday' => Horde_Date::MASK_SATURDAY,
1349: 'sunday' => Horde_Date::MASK_SUNDAY,
1350: );
1351: $days = array(
1352: 'monday' => Horde_Date::DATE_MONDAY,
1353: 'tuesday' => Horde_Date::DATE_TUESDAY,
1354: 'wednesday' => Horde_Date::DATE_WEDNESDAY,
1355: 'thursday' => Horde_Date::DATE_THURSDAY,
1356: 'friday' => Horde_Date::DATE_FRIDAY,
1357: 'saturday' => Horde_Date::DATE_SATURDAY,
1358: 'sunday' => Horde_Date::DATE_SUNDAY,
1359: );
1360:
1361: foreach ($hash['day'] as $day) {
1362:
1363: if (empty($day) || !isset($bits[$day])) {
1364: continue;
1365: }
1366:
1367: $mask |= $bits[$day];
1368: $last_found_day = $days[$day];
1369: }
1370:
1371: if ($set_daymask) {
1372: $this->setRecurOnDay($mask);
1373: }
1374: }
1375:
1376: if ($update_month || $update_daynumber || $update_weekday) {
1377: if ($update_month) {
1378: $month2number = array(
1379: 'january' => 1,
1380: 'february' => 2,
1381: 'march' => 3,
1382: 'april' => 4,
1383: 'may' => 5,
1384: 'june' => 6,
1385: 'july' => 7,
1386: 'august' => 8,
1387: 'september' => 9,
1388: 'october' => 10,
1389: 'november' => 11,
1390: 'december' => 12,
1391: );
1392:
1393: if (isset($month2number[$hash['month']])) {
1394: $this->start->month = $month2number[$hash['month']];
1395: }
1396: }
1397:
1398: if ($update_daynumber) {
1399: if (!isset($hash['daynumber'])) {
1400: $this->setRecurType(self::RECUR_NONE);
1401: return false;
1402: }
1403:
1404: $this->start->mday = $hash['daynumber'];
1405: }
1406:
1407: if ($update_weekday) {
1408: $this->start->setNthWeekday($last_found_day, $nth_weekday);
1409: }
1410: }
1411:
1412:
1413: if (isset($hash['exceptions'])) {
1414: $this->exceptions = $hash['exceptions'];
1415: }
1416:
1417: if (isset($hash['completions'])) {
1418: $this->completions = $hash['completions'];
1419: }
1420:
1421: return true;
1422: }
1423:
1424: 1425: 1426: 1427: 1428:
1429: public function toHash()
1430: {
1431: if ($this->getRecurType() == self::RECUR_NONE) {
1432: return array();
1433: }
1434:
1435: $day2number = array(
1436: 0 => 'sunday',
1437: 1 => 'monday',
1438: 2 => 'tuesday',
1439: 3 => 'wednesday',
1440: 4 => 'thursday',
1441: 5 => 'friday',
1442: 6 => 'saturday'
1443: );
1444: $month2number = array(
1445: 1 => 'january',
1446: 2 => 'february',
1447: 3 => 'march',
1448: 4 => 'april',
1449: 5 => 'may',
1450: 6 => 'june',
1451: 7 => 'july',
1452: 8 => 'august',
1453: 9 => 'september',
1454: 10 => 'october',
1455: 11 => 'november',
1456: 12 => 'december'
1457: );
1458:
1459: $hash = array('interval' => $this->getRecurInterval());
1460: $start = $this->getRecurStart();
1461:
1462: switch ($this->getRecurType()) {
1463: case self::RECUR_DAILY:
1464: $hash['cycle'] = 'daily';
1465: break;
1466:
1467: case self::RECUR_WEEKLY:
1468: $hash['cycle'] = 'weekly';
1469: $bits = array(
1470: 'monday' => Horde_Date::MASK_MONDAY,
1471: 'tuesday' => Horde_Date::MASK_TUESDAY,
1472: 'wednesday' => Horde_Date::MASK_WEDNESDAY,
1473: 'thursday' => Horde_Date::MASK_THURSDAY,
1474: 'friday' => Horde_Date::MASK_FRIDAY,
1475: 'saturday' => Horde_Date::MASK_SATURDAY,
1476: 'sunday' => Horde_Date::MASK_SUNDAY,
1477: );
1478: $days = array();
1479: foreach ($bits as $name => $bit) {
1480: if ($this->recurOnDay($bit)) {
1481: $days[] = $name;
1482: }
1483: }
1484: $hash['day'] = $days;
1485: break;
1486:
1487: case self::RECUR_MONTHLY_DATE:
1488: $hash['cycle'] = 'monthly';
1489: $hash['type'] = 'daynumber';
1490: $hash['daynumber'] = $start->mday;
1491: break;
1492:
1493: case self::RECUR_MONTHLY_WEEKDAY:
1494: $hash['cycle'] = 'monthly';
1495: $hash['type'] = 'weekday';
1496: $hash['daynumber'] = $start->weekOfMonth();
1497: $hash['day'] = array ($day2number[$start->dayOfWeek()]);
1498: break;
1499:
1500: case self::RECUR_YEARLY_DATE:
1501: $hash['cycle'] = 'yearly';
1502: $hash['type'] = 'monthday';
1503: $hash['daynumber'] = $start->mday;
1504: $hash['month'] = $month2number[$start->month];
1505: break;
1506:
1507: case self::RECUR_YEARLY_DAY:
1508: $hash['cycle'] = 'yearly';
1509: $hash['type'] = 'yearday';
1510: $hash['daynumber'] = $start->dayOfYear();
1511: break;
1512:
1513: case self::RECUR_YEARLY_WEEKDAY:
1514: $hash['cycle'] = 'yearly';
1515: $hash['type'] = 'weekday';
1516: $hash['daynumber'] = $start->weekOfMonth();
1517: $hash['day'] = array ($day2number[$start->dayOfWeek()]);
1518: $hash['month'] = $month2number[$start->month];
1519: }
1520:
1521: if ($this->hasRecurCount()) {
1522: $hash['range-type'] = 'number';
1523: $hash['range'] = $this->getRecurCount();
1524: } elseif ($this->hasRecurEnd()) {
1525: $date = $this->getRecurEnd();
1526: $hash['range-type'] = 'date';
1527: $hash['range'] = $date->datestamp();
1528: } else {
1529: $hash['range-type'] = 'none';
1530: $hash['range'] = '';
1531: }
1532:
1533:
1534: $hash['exceptions'] = $this->exceptions;
1535: $hash['completions'] = $this->completions;
1536:
1537: return $hash;
1538: }
1539:
1540: 1541: 1542: 1543: 1544: 1545: 1546: 1547: 1548: 1549: 1550: 1551: 1552: 1553: 1554:
1555: public function toJson()
1556: {
1557: $json = new stdClass;
1558: $json->t = $this->recurType;
1559: $json->i = $this->recurInterval;
1560: if ($this->hasRecurEnd()) {
1561: $json->e = $this->recurEnd->toJson();
1562: }
1563: if ($this->recurCount) {
1564: $json->c = $this->recurCount;
1565: }
1566: if ($this->recurData) {
1567: $json->d = $this->recurData;
1568: }
1569: if ($this->completions) {
1570: $json->co = $this->completions;
1571: }
1572: if ($this->exceptions) {
1573: $json->ex = $this->exceptions;
1574: }
1575: return $json;
1576: }
1577:
1578: }
1579: