001
014
015
044
045 package com.liferay.portal.kernel.cal;
046
047 import com.liferay.portal.kernel.util.CalendarFactoryUtil;
048 import com.liferay.portal.kernel.util.StringBundler;
049 import com.liferay.portal.kernel.util.StringPool;
050 import com.liferay.portal.kernel.util.TimeZoneUtil;
051
052 import java.io.Serializable;
053
054 import java.util.Calendar;
055 import java.util.Date;
056
057
060 public class Recurrence implements Serializable {
061
062
065 public static final int DAILY = 3;
066
067
070 public static final int WEEKLY = 4;
071
072
075 public static final int MONTHLY = 5;
076
077
080 public static final int YEARLY = 6;
081
082
085 public static final int NO_RECURRENCE = 7;
086
087
090 protected Calendar dtStart;
091
092
095 protected Duration duration;
096
097
100 protected int frequency;
101
102
105 protected int interval;
106
107
110 protected int occurrence = 0;
111
112
115 protected Calendar until;
116
117
120 protected DayAndPosition[] byDay;
121
122
125 protected int[] byMonthDay;
126
127
130 protected int[] byYearDay;
131
132
135 protected int[] byWeekNo;
136
137
140 protected int[] byMonth;
141
142
145 public Recurrence() {
146 this(null, new Duration(), NO_RECURRENCE);
147 }
148
149
152 public Recurrence(Calendar start, Duration dur) {
153 this(start, dur, NO_RECURRENCE);
154 }
155
156
159 public Recurrence(Calendar start, Duration dur, int freq) {
160 setDtStart(start);
161
162 duration = (Duration)dur.clone();
163 frequency = freq;
164 interval = 1;
165 }
166
167
168
169
174 public Calendar getDtStart() {
175 return (Calendar)dtStart.clone();
176 }
177
178
181 public void setDtStart(Calendar start) {
182 int oldStart;
183
184 if (dtStart != null) {
185 oldStart = dtStart.getFirstDayOfWeek();
186 }
187 else {
188 oldStart = Calendar.MONDAY;
189 }
190
191 if (start == null) {
192 dtStart = CalendarFactoryUtil.getCalendar(
193 TimeZoneUtil.getTimeZone(StringPool.UTC));
194
195 dtStart.setTime(new Date(0L));
196 }
197 else {
198 dtStart = (Calendar)start.clone();
199
200 dtStart.clear(Calendar.ZONE_OFFSET);
201 dtStart.clear(Calendar.DST_OFFSET);
202 dtStart.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
203 }
204
205 dtStart.setMinimalDaysInFirstWeek(4);
206 dtStart.setFirstDayOfWeek(oldStart);
207 }
208
209
214 public Duration getDuration() {
215 return (Duration)duration.clone();
216 }
217
218
221 public void setDuration(Duration d) {
222 duration = (Duration)d.clone();
223 }
224
225
230 public Calendar getDtEnd() {
231
232
236 Calendar tempEnd = (Calendar)dtStart.clone();
237
238 tempEnd.setTime(new Date(dtStart.getTime().getTime()
239 + duration.getInterval()));
240
241 return tempEnd;
242 }
243
244
247 public void setDtEnd(Calendar end) {
248 Calendar tempEnd = (Calendar)end.clone();
249
250 tempEnd.clear(Calendar.ZONE_OFFSET);
251 tempEnd.clear(Calendar.DST_OFFSET);
252 tempEnd.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
253 duration.setInterval(tempEnd.getTime().getTime()
254 - dtStart.getTime().getTime());
255 }
256
257
262 public int getFrequency() {
263 return frequency;
264 }
265
266
269 public void setFrequency(int freq) {
270 if ((frequency != DAILY) && (frequency != WEEKLY)
271 && (frequency != MONTHLY) && (frequency != YEARLY)
272 && (frequency != NO_RECURRENCE)) {
273 throw new IllegalArgumentException("Invalid frequency");
274 }
275
276 frequency = freq;
277 }
278
279
284 public int getInterval() {
285 return interval;
286 }
287
288
291 public void setInterval(int intr) {
292 interval = (intr > 0) ? intr : 1;
293 }
294
295
300 public int getOccurrence() {
301 return occurrence;
302 }
303
304
307 public void setOccurrence(int occur) {
308 occurrence = occur;
309 }
310
311
316 public Calendar getUntil() {
317 return ((until != null) ? (Calendar)until.clone() : null);
318 }
319
320
323 public void setUntil(Calendar u) {
324 if (u == null) {
325 until = null;
326
327 return;
328 }
329
330 until = (Calendar)u.clone();
331
332 until.clear(Calendar.ZONE_OFFSET);
333 until.clear(Calendar.DST_OFFSET);
334 until.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
335 }
336
337
342 public int getWeekStart() {
343 return dtStart.getFirstDayOfWeek();
344 }
345
346
349 public void setWeekStart(int weekstart) {
350 dtStart.setFirstDayOfWeek(weekstart);
351 }
352
353
358 public DayAndPosition[] getByDay() {
359 if (byDay == null) {
360 return null;
361 }
362
363 DayAndPosition[] b = new DayAndPosition[byDay.length];
364
365
369 for (int i = 0; i < byDay.length; i++) {
370 b[i] = (DayAndPosition)byDay[i].clone();
371 }
372
373 return b;
374 }
375
376
379 public void setByDay(DayAndPosition[] b) {
380 if (b == null) {
381 byDay = null;
382
383 return;
384 }
385
386 byDay = new DayAndPosition[b.length];
387
388
392 for (int i = 0; i < b.length; i++) {
393 byDay[i] = (DayAndPosition)b[i].clone();
394 }
395 }
396
397
402 public int[] getByMonthDay() {
403 if (byMonthDay == null) {
404 return null;
405 }
406
407 int[] b = new int[byMonthDay.length];
408
409 System.arraycopy(byMonthDay, 0, b, 0, byMonthDay.length);
410
411 return b;
412 }
413
414
417 public void setByMonthDay(int[] b) {
418 if (b == null) {
419 byMonthDay = null;
420
421 return;
422 }
423
424 byMonthDay = new int[b.length];
425
426 System.arraycopy(b, 0, byMonthDay, 0, b.length);
427 }
428
429
434 public int[] getByYearDay() {
435 if (byYearDay == null) {
436 return null;
437 }
438
439 int[] b = new int[byYearDay.length];
440
441 System.arraycopy(byYearDay, 0, b, 0, byYearDay.length);
442
443 return b;
444 }
445
446
449 public void setByYearDay(int[] b) {
450 if (b == null) {
451 byYearDay = null;
452
453 return;
454 }
455
456 byYearDay = new int[b.length];
457
458 System.arraycopy(b, 0, byYearDay, 0, b.length);
459 }
460
461
466 public int[] getByWeekNo() {
467 if (byWeekNo == null) {
468 return null;
469 }
470
471 int[] b = new int[byWeekNo.length];
472
473 System.arraycopy(byWeekNo, 0, b, 0, byWeekNo.length);
474
475 return b;
476 }
477
478
481 public void setByWeekNo(int[] b) {
482 if (b == null) {
483 byWeekNo = null;
484
485 return;
486 }
487
488 byWeekNo = new int[b.length];
489
490 System.arraycopy(b, 0, byWeekNo, 0, b.length);
491 }
492
493
498 public int[] getByMonth() {
499 if (byMonth == null) {
500 return null;
501 }
502
503 int[] b = new int[byMonth.length];
504
505 System.arraycopy(byMonth, 0, b, 0, byMonth.length);
506
507 return b;
508 }
509
510
513 public void setByMonth(int[] b) {
514 if (b == null) {
515 byMonth = null;
516
517 return;
518 }
519
520 byMonth = new int[b.length];
521
522 System.arraycopy(b, 0, byMonth, 0, b.length);
523 }
524
525
530 public boolean isInRecurrence(Calendar current) {
531 return isInRecurrence(current, false);
532 }
533
534
539 public boolean isInRecurrence(Calendar current, boolean debug) {
540 Calendar myCurrent = (Calendar)current.clone();
541
542
543
544 myCurrent.clear(Calendar.ZONE_OFFSET);
545 myCurrent.clear(Calendar.DST_OFFSET);
546 myCurrent.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
547 myCurrent.setMinimalDaysInFirstWeek(4);
548 myCurrent.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
549
550 if (myCurrent.getTime().getTime() < dtStart.getTime().getTime()) {
551
552
553
554 if (debug) {
555 System.err.println("current < start");
556 }
557
558 return false;
559 }
560
561 if (myCurrent.getTime().getTime()
562 < dtStart.getTime().getTime() + duration.getInterval()) {
563
564
565
566 if (debug) {
567 System.err.println("within duration of start");
568 }
569
570 return true;
571 }
572
573 Calendar candidate = getCandidateStartTime(myCurrent);
574
575
576
577 while (candidate.getTime().getTime() + duration.getInterval()
578 > myCurrent.getTime().getTime()) {
579 if (candidateIsInRecurrence(candidate, debug)) {
580 return true;
581 }
582
583
584
585 candidate.add(Calendar.SECOND, -1);
586
587
588
589 if (candidate.getTime().getTime() < dtStart.getTime().getTime()) {
590 if (debug) {
591 System.err.println("No candidates after dtStart");
592 }
593
594 return false;
595 }
596
597 candidate = getCandidateStartTime(candidate);
598 }
599
600 if (debug) {
601 System.err.println("No matching candidates");
602 }
603
604 return false;
605 }
606
607
612 protected boolean candidateIsInRecurrence(Calendar candidate,
613 boolean debug) {
614 if ((until != null)
615 && (candidate.getTime().getTime() > until.getTime().getTime())) {
616
617
618
619 if (debug) {
620 System.err.println("after until");
621 }
622
623 return false;
624 }
625
626 if (getRecurrenceCount(candidate) % interval != 0) {
627
628
629
630 if (debug) {
631 System.err.println("not an interval rep");
632 }
633
634 return false;
635 }
636 else if ((occurrence > 0) &&
637 (getRecurrenceCount(candidate) >= occurrence)) {
638
639 return false;
640 }
641
642 if (!matchesByDay(candidate) ||!matchesByMonthDay(candidate)
643 ||!matchesByYearDay(candidate) ||!matchesByWeekNo(candidate)
644 ||!matchesByMonth(candidate)) {
645
646
647
648 if (debug) {
649 System.err.println("doesn't match a by*");
650 }
651
652 return false;
653 }
654
655 if (debug) {
656 System.err.println("All checks succeeded");
657 }
658
659 return true;
660 }
661
662
667 protected int getMinimumInterval() {
668 if ((frequency == DAILY) || (byDay != null) || (byMonthDay != null)
669 || (byYearDay != null)) {
670 return DAILY;
671 }
672 else if ((frequency == WEEKLY) || (byWeekNo != null)) {
673 return WEEKLY;
674 }
675 else if ((frequency == MONTHLY) || (byMonth != null)) {
676 return MONTHLY;
677 }
678 else if (frequency == YEARLY) {
679 return YEARLY;
680 }
681 else if (frequency == NO_RECURRENCE) {
682 return NO_RECURRENCE;
683 }
684 else {
685
686
687
688 throw new IllegalStateException(
689 "Internal error: Unknown frequency value");
690 }
691 }
692
693
698 public Calendar getCandidateStartTime(Calendar current) {
699 if (dtStart.getTime().getTime() > current.getTime().getTime()) {
700 throw new IllegalArgumentException("Current time before DtStart");
701 }
702
703 int minInterval = getMinimumInterval();
704 Calendar candidate = (Calendar)current.clone();
705
706 if (true) {
707
708
709
710 candidate.clear(Calendar.ZONE_OFFSET);
711 candidate.clear(Calendar.DST_OFFSET);
712 candidate.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
713 candidate.setMinimalDaysInFirstWeek(4);
714 candidate.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
715 }
716
717 if (frequency == NO_RECURRENCE) {
718 candidate.setTime(dtStart.getTime());
719
720 return candidate;
721 }
722
723 reduce_constant_length_field(Calendar.SECOND, dtStart, candidate);
724 reduce_constant_length_field(Calendar.MINUTE, dtStart, candidate);
725 reduce_constant_length_field(Calendar.HOUR_OF_DAY, dtStart, candidate);
726
727 switch (minInterval) {
728
729 case DAILY :
730
731
732
733 break;
734
735 case WEEKLY :
736 reduce_constant_length_field(Calendar.DAY_OF_WEEK, dtStart,
737 candidate);
738 break;
739
740 case MONTHLY :
741 reduce_day_of_month(dtStart, candidate);
742 break;
743
744 case YEARLY :
745 reduce_day_of_year(dtStart, candidate);
746 break;
747 }
748
749 return candidate;
750 }
751
752
755 protected static void reduce_constant_length_field(int field,
756 Calendar start,
757 Calendar candidate) {
758 if ((start.getMaximum(field) != start.getLeastMaximum(field))
759 || (start.getMinimum(field) != start.getGreatestMinimum(field))) {
760 throw new IllegalArgumentException("Not a constant length field");
761 }
762
763 int fieldLength = (start.getMaximum(field) - start.getMinimum(field)
764 + 1);
765 int delta = start.get(field) - candidate.get(field);
766
767 if (delta > 0) {
768 delta -= fieldLength;
769 }
770
771 candidate.add(field, delta);
772 }
773
774
777 protected static void reduce_day_of_month(Calendar start,
778 Calendar candidate) {
779 Calendar tempCal = (Calendar)candidate.clone();
780
781 tempCal.add(Calendar.MONTH, -1);
782
783 int delta = start.get(Calendar.DATE) - candidate.get(Calendar.DATE);
784
785 if (delta > 0) {
786 delta -= tempCal.getActualMaximum(Calendar.DATE);
787 }
788
789 candidate.add(Calendar.DATE, delta);
790
791 while (start.get(Calendar.DATE) != candidate.get(Calendar.DATE)) {
792 tempCal.add(Calendar.MONTH, -1);
793 candidate.add(Calendar.DATE,
794 -tempCal.getActualMaximum(Calendar.DATE));
795 }
796 }
797
798
801 protected static void reduce_day_of_year(Calendar start,
802 Calendar candidate) {
803 if ((start.get(Calendar.MONTH) > candidate.get(Calendar.MONTH))
804 || ((start.get(Calendar.MONTH) == candidate.get(Calendar.MONTH))
805 && (start.get(Calendar.DATE) > candidate.get(Calendar.DATE)))) {
806 candidate.add(Calendar.YEAR, -1);
807 }
808
809
810
811 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
812 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
813
814 while ((start.get(Calendar.MONTH) != candidate.get(Calendar.MONTH))
815 || (start.get(Calendar.DATE) != candidate.get(Calendar.DATE))) {
816 candidate.add(Calendar.YEAR, -1);
817 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
818 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
819 }
820 }
821
822
827 protected int getRecurrenceCount(Calendar candidate) {
828 switch (frequency) {
829
830 case NO_RECURRENCE :
831 return 0;
832
833 case DAILY :
834 return (int)(getDayNumber(candidate) - getDayNumber(dtStart));
835
836 case WEEKLY :
837 Calendar tempCand = (Calendar)candidate.clone();
838
839 tempCand.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
840
841 return (int)(getWeekNumber(tempCand) - getWeekNumber(dtStart));
842
843 case MONTHLY :
844 return (int)(getMonthNumber(candidate)
845 - getMonthNumber(dtStart));
846
847 case YEARLY :
848 return candidate.get(Calendar.YEAR)
849 - dtStart.get(Calendar.YEAR);
850
851 default :
852 throw new IllegalStateException("bad frequency internally...");
853 }
854 }
855
856
861 protected static long getDayNumber(Calendar cal) {
862 Calendar tempCal = (Calendar)cal.clone();
863
864
865
866 tempCal.set(Calendar.MILLISECOND, 0);
867 tempCal.set(Calendar.SECOND, 0);
868 tempCal.set(Calendar.MINUTE, 0);
869 tempCal.set(Calendar.HOUR_OF_DAY, 0);
870
871 return tempCal.getTime().getTime() / (24 * 60 * 60 * 1000);
872 }
873
874
879 protected static long getWeekNumber(Calendar cal) {
880 Calendar tempCal = (Calendar)cal.clone();
881
882
883
884 tempCal.set(Calendar.MILLISECOND, 0);
885 tempCal.set(Calendar.SECOND, 0);
886 tempCal.set(Calendar.MINUTE, 0);
887 tempCal.set(Calendar.HOUR_OF_DAY, 0);
888
889
890
891 int delta = tempCal.getFirstDayOfWeek()
892 - tempCal.get(Calendar.DAY_OF_WEEK);
893
894 if (delta > 0) {
895 delta -= 7;
896 }
897
898
899
900
901
902
903 long weekEpoch = (tempCal.getFirstDayOfWeek() - Calendar.THURSDAY) * 24
904 * 60 * 60 * 1000L;
905
906 return (tempCal.getTime().getTime() - weekEpoch)
907 / (7 * 24 * 60 * 60 * 1000);
908 }
909
910
915 protected static long getMonthNumber(Calendar cal) {
916 return (cal.get(Calendar.YEAR) - 1970) * 12
917 + (cal.get(Calendar.MONTH) - Calendar.JANUARY);
918 }
919
920
925 protected boolean matchesByDay(Calendar candidate) {
926 if ((byDay == null) || (byDay.length == 0)) {
927
928
929
930 return true;
931 }
932
933 int i;
934
935 for (i = 0; i < byDay.length; i++) {
936 if (matchesIndividualByDay(candidate, byDay[i])) {
937 return true;
938 }
939 }
940
941 return false;
942 }
943
944
949 protected boolean matchesIndividualByDay(Calendar candidate,
950 DayAndPosition pos) {
951 if (pos.getDayOfWeek() != candidate.get(Calendar.DAY_OF_WEEK)) {
952 return false;
953 }
954
955 int position = pos.getDayPosition();
956
957 if (position == 0) {
958 return true;
959 }
960
961 int field = Calendar.DAY_OF_MONTH;
962
963 if (position > 0) {
964 int candidatePosition = ((candidate.get(field) - 1) / 7) + 1;
965
966 return (position == candidatePosition);
967 }
968 else {
969
970
971
972 int negativeCandidatePosition =
973 ((candidate.getActualMaximum(field) - candidate.get(field)) / 7)
974 + 1;
975
976 return (-position == negativeCandidatePosition);
977 }
978 }
979
980
985 protected static boolean matchesByField(int[] array, int field,
986 Calendar candidate,
987 boolean allowNegative) {
988 if ((array == null) || (array.length == 0)) {
989
990
991
992 return true;
993 }
994
995 int i;
996
997 for (i = 0; i < array.length; i++) {
998 int val;
999
1000 if (allowNegative && (array[i] < 0)) {
1001
1002
1003
1004 int max = candidate.getActualMaximum(field);
1005
1006 val = (max + 1) + array[i];
1007 }
1008 else {
1009 val = array[i];
1010 }
1011
1012 if (val == candidate.get(field)) {
1013 return true;
1014 }
1015 }
1016
1017 return false;
1018 }
1019
1020
1025 protected boolean matchesByMonthDay(Calendar candidate) {
1026 return matchesByField(byMonthDay, Calendar.DATE, candidate, true);
1027 }
1028
1029
1034 protected boolean matchesByYearDay(Calendar candidate) {
1035 return matchesByField(byYearDay, Calendar.DAY_OF_YEAR, candidate, true);
1036 }
1037
1038
1043 protected boolean matchesByWeekNo(Calendar candidate) {
1044 return matchesByField(byWeekNo, Calendar.WEEK_OF_YEAR, candidate, true);
1045 }
1046
1047
1052 protected boolean matchesByMonth(Calendar candidate) {
1053 return matchesByField(byMonth, Calendar.MONTH, candidate, false);
1054 }
1055
1056
1061 public String toString() {
1062 StringBundler sb = new StringBundler();
1063
1064 sb.append(getClass().getName());
1065 sb.append("[dtStart=");
1066 sb.append((dtStart != null) ? dtStart.toString() : "null");
1067 sb.append(",duration=");
1068 sb.append((duration != null) ? duration.toString() : "null");
1069 sb.append(",frequency=");
1070 sb.append(frequency);
1071 sb.append(",interval=");
1072 sb.append(interval);
1073 sb.append(",until=");
1074 sb.append((until != null) ? until.toString() : "null");
1075 sb.append(",byDay=");
1076
1077 if (byDay == null) {
1078 sb.append("null");
1079 }
1080 else {
1081 sb.append("[");
1082
1083 for (int i = 0; i < byDay.length; i++) {
1084 if (i != 0) {
1085 sb.append(",");
1086 }
1087
1088 if (byDay[i] != null) {
1089 sb.append(byDay[i].toString());
1090 }
1091 else {
1092 sb.append("null");
1093 }
1094 }
1095
1096 sb.append("]");
1097 }
1098
1099 sb.append(",byMonthDay=");
1100 sb.append(stringizeIntArray(byMonthDay));
1101 sb.append(",byYearDay=");
1102 sb.append(stringizeIntArray(byYearDay));
1103 sb.append(",byWeekNo=");
1104 sb.append(stringizeIntArray(byWeekNo));
1105 sb.append(",byMonth=");
1106 sb.append(stringizeIntArray(byMonth));
1107 sb.append(']');
1108
1109 return sb.toString();
1110 }
1111
1112
1117 private String stringizeIntArray(int[] a) {
1118 if (a == null) {
1119 return "null";
1120 }
1121
1122 StringBundler sb = new StringBundler(2 * a.length + 1);
1123
1124 sb.append("[");
1125
1126 for (int i = 0; i < a.length; i++) {
1127 if (i != 0) {
1128 sb.append(",");
1129 }
1130
1131 sb.append(a[i]);
1132 }
1133
1134 sb.append("]");
1135
1136 return sb.toString();
1137 }
1138
1139 }