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 Calendar candidate = getCandidateStartTime(myCurrent);
562
563
564
565 while (candidate.getTime().getTime() + duration.getInterval()
566 > myCurrent.getTime().getTime()) {
567 if (candidateIsInRecurrence(candidate, debug)) {
568 return true;
569 }
570
571
572
573 candidate.add(Calendar.SECOND, -1);
574
575
576
577 if (candidate.getTime().getTime() < dtStart.getTime().getTime()) {
578 if (debug) {
579 System.err.println("No candidates after dtStart");
580 }
581
582 return false;
583 }
584
585 candidate = getCandidateStartTime(candidate);
586 }
587
588 if (debug) {
589 System.err.println("No matching candidates");
590 }
591
592 return false;
593 }
594
595
600 protected boolean candidateIsInRecurrence(Calendar candidate,
601 boolean debug) {
602 if ((until != null)
603 && (candidate.getTime().getTime() > until.getTime().getTime())) {
604
605
606
607 if (debug) {
608 System.err.println("after until");
609 }
610
611 return false;
612 }
613
614 if (getRecurrenceCount(candidate) % interval != 0) {
615
616
617
618 if (debug) {
619 System.err.println("not an interval rep");
620 }
621
622 return false;
623 }
624 else if ((occurrence > 0) &&
625 (getRecurrenceCount(candidate) >= occurrence)) {
626
627 return false;
628 }
629
630 if (!matchesByDay(candidate) ||!matchesByMonthDay(candidate)
631 ||!matchesByYearDay(candidate) ||!matchesByWeekNo(candidate)
632 ||!matchesByMonth(candidate)) {
633
634
635
636 if (debug) {
637 System.err.println("doesn't match a by*");
638 }
639
640 return false;
641 }
642
643 if (debug) {
644 System.err.println("All checks succeeded");
645 }
646
647 return true;
648 }
649
650
655 protected int getMinimumInterval() {
656 if ((frequency == DAILY) || (byDay != null) || (byMonthDay != null)
657 || (byYearDay != null)) {
658 return DAILY;
659 }
660 else if ((frequency == WEEKLY) || (byWeekNo != null)) {
661 return WEEKLY;
662 }
663 else if ((frequency == MONTHLY) || (byMonth != null)) {
664 return MONTHLY;
665 }
666 else if (frequency == YEARLY) {
667 return YEARLY;
668 }
669 else if (frequency == NO_RECURRENCE) {
670 return NO_RECURRENCE;
671 }
672 else {
673
674
675
676 throw new IllegalStateException(
677 "Internal error: Unknown frequency value");
678 }
679 }
680
681
686 public Calendar getCandidateStartTime(Calendar current) {
687 if (dtStart.getTime().getTime() > current.getTime().getTime()) {
688 throw new IllegalArgumentException("Current time before DtStart");
689 }
690
691 int minInterval = getMinimumInterval();
692 Calendar candidate = (Calendar)current.clone();
693
694 if (true) {
695
696
697
698 candidate.clear(Calendar.ZONE_OFFSET);
699 candidate.clear(Calendar.DST_OFFSET);
700 candidate.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
701 candidate.setMinimalDaysInFirstWeek(4);
702 candidate.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
703 }
704
705 if (frequency == NO_RECURRENCE) {
706 candidate.setTime(dtStart.getTime());
707
708 return candidate;
709 }
710
711 reduce_constant_length_field(Calendar.SECOND, dtStart, candidate);
712 reduce_constant_length_field(Calendar.MINUTE, dtStart, candidate);
713 reduce_constant_length_field(Calendar.HOUR_OF_DAY, dtStart, candidate);
714
715 switch (minInterval) {
716
717 case DAILY :
718
719
720
721 break;
722
723 case WEEKLY :
724 reduce_constant_length_field(Calendar.DAY_OF_WEEK, dtStart,
725 candidate);
726 break;
727
728 case MONTHLY :
729 reduce_day_of_month(dtStart, candidate);
730 break;
731
732 case YEARLY :
733 reduce_day_of_year(dtStart, candidate);
734 break;
735 }
736
737 return candidate;
738 }
739
740
743 protected void reduce_constant_length_field(int field,
744 Calendar start,
745 Calendar candidate) {
746 if ((start.getMaximum(field) != start.getLeastMaximum(field))
747 || (start.getMinimum(field) != start.getGreatestMinimum(field))) {
748 throw new IllegalArgumentException("Not a constant length field");
749 }
750
751 int fieldLength = (start.getMaximum(field) - start.getMinimum(field)
752 + 1);
753 int delta = start.get(field) - candidate.get(field);
754
755 if (delta > 0) {
756 delta -= fieldLength;
757 }
758
759 candidate.add(field, delta);
760 }
761
762
765 protected static void reduce_day_of_month(Calendar start,
766 Calendar candidate) {
767 Calendar tempCal = (Calendar)candidate.clone();
768
769 tempCal.add(Calendar.MONTH, -1);
770
771 int delta = start.get(Calendar.DATE) - candidate.get(Calendar.DATE);
772
773 if (delta > 0) {
774 delta -= tempCal.getActualMaximum(Calendar.DATE);
775 }
776
777 candidate.add(Calendar.DATE, delta);
778
779 while (start.get(Calendar.DATE) != candidate.get(Calendar.DATE)) {
780 tempCal.add(Calendar.MONTH, -1);
781 candidate.add(Calendar.DATE,
782 -tempCal.getActualMaximum(Calendar.DATE));
783 }
784 }
785
786
789 protected static void reduce_day_of_year(Calendar start,
790 Calendar candidate) {
791 if ((start.get(Calendar.MONTH) > candidate.get(Calendar.MONTH))
792 || ((start.get(Calendar.MONTH) == candidate.get(Calendar.MONTH))
793 && (start.get(Calendar.DATE) > candidate.get(Calendar.DATE)))) {
794 candidate.add(Calendar.YEAR, -1);
795 }
796
797
798
799 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
800 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
801
802 while ((start.get(Calendar.MONTH) != candidate.get(Calendar.MONTH))
803 || (start.get(Calendar.DATE) != candidate.get(Calendar.DATE))) {
804 candidate.add(Calendar.YEAR, -1);
805 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
806 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
807 }
808 }
809
810
815 protected int getRecurrenceCount(Calendar candidate) {
816 switch (frequency) {
817
818 case NO_RECURRENCE :
819 return 0;
820
821 case DAILY :
822 return (int)(getDayNumber(candidate) - getDayNumber(dtStart));
823
824 case WEEKLY :
825 Calendar tempCand = (Calendar)candidate.clone();
826
827 tempCand.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
828
829 return (int)(getWeekNumber(tempCand) - getWeekNumber(dtStart));
830
831 case MONTHLY :
832 return (int)(getMonthNumber(candidate)
833 - getMonthNumber(dtStart));
834
835 case YEARLY :
836 return candidate.get(Calendar.YEAR)
837 - dtStart.get(Calendar.YEAR);
838
839 default :
840 throw new IllegalStateException("bad frequency internally...");
841 }
842 }
843
844
849 protected static long getDayNumber(Calendar cal) {
850 Calendar tempCal = (Calendar)cal.clone();
851
852
853
854 tempCal.set(Calendar.MILLISECOND, 0);
855 tempCal.set(Calendar.SECOND, 0);
856 tempCal.set(Calendar.MINUTE, 0);
857 tempCal.set(Calendar.HOUR_OF_DAY, 0);
858
859 return tempCal.getTime().getTime() / (24 * 60 * 60 * 1000);
860 }
861
862
867 protected static long getWeekNumber(Calendar cal) {
868 Calendar tempCal = (Calendar)cal.clone();
869
870
871
872 tempCal.set(Calendar.MILLISECOND, 0);
873 tempCal.set(Calendar.SECOND, 0);
874 tempCal.set(Calendar.MINUTE, 0);
875 tempCal.set(Calendar.HOUR_OF_DAY, 0);
876
877
878
879 int delta = tempCal.getFirstDayOfWeek()
880 - tempCal.get(Calendar.DAY_OF_WEEK);
881
882 if (delta > 0) {
883 delta -= 7;
884 }
885
886
887
888
889
890
891 long weekEpoch = (tempCal.getFirstDayOfWeek() - Calendar.THURSDAY) * 24L
892 * 60 * 60 * 1000;
893
894 return (tempCal.getTime().getTime() - weekEpoch)
895 / (7 * 24 * 60 * 60 * 1000);
896 }
897
898
903 protected static long getMonthNumber(Calendar cal) {
904 return (cal.get(Calendar.YEAR) - 1970) * 12L
905 + (cal.get(Calendar.MONTH) - Calendar.JANUARY);
906 }
907
908
913 protected boolean matchesByDay(Calendar candidate) {
914 if ((byDay == null) || (byDay.length == 0)) {
915
916
917
918 return true;
919 }
920
921 int i;
922
923 for (i = 0; i < byDay.length; i++) {
924 if (matchesIndividualByDay(candidate, byDay[i])) {
925 return true;
926 }
927 }
928
929 return false;
930 }
931
932
937 protected boolean matchesIndividualByDay(Calendar candidate,
938 DayAndPosition pos) {
939 if (pos.getDayOfWeek() != candidate.get(Calendar.DAY_OF_WEEK)) {
940 return false;
941 }
942
943 int position = pos.getDayPosition();
944
945 if (position == 0) {
946 return true;
947 }
948
949 int field = Calendar.DAY_OF_MONTH;
950
951 if (position > 0) {
952 int candidatePosition = ((candidate.get(field) - 1) / 7) + 1;
953
954 return (position == candidatePosition);
955 }
956 else {
957
958
959
960 int negativeCandidatePosition =
961 ((candidate.getActualMaximum(field) - candidate.get(field)) / 7)
962 + 1;
963
964 return (-position == negativeCandidatePosition);
965 }
966 }
967
968
973 protected boolean matchesByField(int[] array, int field,
974 Calendar candidate,
975 boolean allowNegative) {
976 if ((array == null) || (array.length == 0)) {
977
978
979
980 return true;
981 }
982
983 int i;
984
985 for (i = 0; i < array.length; i++) {
986 int val;
987
988 if (allowNegative && (array[i] < 0)) {
989
990
991
992 int max = candidate.getActualMaximum(field);
993
994 val = (max + 1) + array[i];
995 }
996 else {
997 val = array[i];
998 }
999
1000 if (val == candidate.get(field)) {
1001 return true;
1002 }
1003 }
1004
1005 return false;
1006 }
1007
1008
1013 protected boolean matchesByMonthDay(Calendar candidate) {
1014 return matchesByField(byMonthDay, Calendar.DATE, candidate, true);
1015 }
1016
1017
1022 protected boolean matchesByYearDay(Calendar candidate) {
1023 return matchesByField(byYearDay, Calendar.DAY_OF_YEAR, candidate, true);
1024 }
1025
1026
1031 protected boolean matchesByWeekNo(Calendar candidate) {
1032 return matchesByField(byWeekNo, Calendar.WEEK_OF_YEAR, candidate, true);
1033 }
1034
1035
1040 protected boolean matchesByMonth(Calendar candidate) {
1041 return matchesByField(byMonth, Calendar.MONTH, candidate, false);
1042 }
1043
1044
1049 @Override
1050 public String toString() {
1051 StringBundler sb = new StringBundler();
1052
1053 sb.append(getClass().getName());
1054 sb.append("[dtStart=");
1055 sb.append((dtStart != null) ? dtStart.toString() : "null");
1056 sb.append(",duration=");
1057 sb.append((duration != null) ? duration.toString() : "null");
1058 sb.append(",frequency=");
1059 sb.append(frequency);
1060 sb.append(",interval=");
1061 sb.append(interval);
1062 sb.append(",until=");
1063 sb.append((until != null) ? until.toString() : "null");
1064 sb.append(",byDay=");
1065
1066 if (byDay == null) {
1067 sb.append("null");
1068 }
1069 else {
1070 sb.append("[");
1071
1072 for (int i = 0; i < byDay.length; i++) {
1073 if (i != 0) {
1074 sb.append(",");
1075 }
1076
1077 if (byDay[i] != null) {
1078 sb.append(byDay[i].toString());
1079 }
1080 else {
1081 sb.append("null");
1082 }
1083 }
1084
1085 sb.append("]");
1086 }
1087
1088 sb.append(",byMonthDay=");
1089 sb.append(stringizeIntArray(byMonthDay));
1090 sb.append(",byYearDay=");
1091 sb.append(stringizeIntArray(byYearDay));
1092 sb.append(",byWeekNo=");
1093 sb.append(stringizeIntArray(byWeekNo));
1094 sb.append(",byMonth=");
1095 sb.append(stringizeIntArray(byMonth));
1096 sb.append(']');
1097
1098 return sb.toString();
1099 }
1100
1101
1106 private String stringizeIntArray(int[] a) {
1107 if (a == null) {
1108 return "null";
1109 }
1110
1111 StringBundler sb = new StringBundler(2 * a.length + 1);
1112
1113 sb.append("[");
1114
1115 for (int i = 0; i < a.length; i++) {
1116 if (i != 0) {
1117 sb.append(",");
1118 }
1119
1120 sb.append(a[i]);
1121 }
1122
1123 sb.append("]");
1124
1125 return sb.toString();
1126 }
1127
1128 }