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 MONTHLY = 5;
071
072
075 public static final int NO_RECURRENCE = 7;
076
077
080 public static final int WEEKLY = 4;
081
082
085 public static final int YEARLY = 6;
086
087
090 public Recurrence() {
091 this(null, new Duration(), NO_RECURRENCE);
092 }
093
094
097 public Recurrence(Calendar start, Duration dur) {
098 this(start, dur, NO_RECURRENCE);
099 }
100
101
104 public Recurrence(Calendar start, Duration dur, int freq) {
105 setDtStart(start);
106
107 duration = (Duration)dur.clone();
108 frequency = freq;
109 interval = 1;
110 }
111
112
113
114
119 public DayAndPosition[] getByDay() {
120 if (byDay == null) {
121 return null;
122 }
123
124 DayAndPosition[] b = new DayAndPosition[byDay.length];
125
126
130 for (int i = 0; i < byDay.length; i++) {
131 b[i] = (DayAndPosition)byDay[i].clone();
132 }
133
134 return b;
135 }
136
137
142 public int[] getByMonth() {
143 if (byMonth == null) {
144 return null;
145 }
146
147 int[] b = new int[byMonth.length];
148
149 System.arraycopy(byMonth, 0, b, 0, byMonth.length);
150
151 return b;
152 }
153
154
159 public int[] getByMonthDay() {
160 if (byMonthDay == null) {
161 return null;
162 }
163
164 int[] b = new int[byMonthDay.length];
165
166 System.arraycopy(byMonthDay, 0, b, 0, byMonthDay.length);
167
168 return b;
169 }
170
171
176 public int[] getByWeekNo() {
177 if (byWeekNo == null) {
178 return null;
179 }
180
181 int[] b = new int[byWeekNo.length];
182
183 System.arraycopy(byWeekNo, 0, b, 0, byWeekNo.length);
184
185 return b;
186 }
187
188
193 public int[] getByYearDay() {
194 if (byYearDay == null) {
195 return null;
196 }
197
198 int[] b = new int[byYearDay.length];
199
200 System.arraycopy(byYearDay, 0, b, 0, byYearDay.length);
201
202 return b;
203 }
204
205
210 public Calendar getCandidateStartTime(Calendar current) {
211 if (dtStart.getTime().getTime() > current.getTime().getTime()) {
212 throw new IllegalArgumentException("Current time before DtStart");
213 }
214
215 int minInterval = getMinimumInterval();
216 Calendar candidate = (Calendar)current.clone();
217
218 if (true) {
219
220
221
222 candidate.clear(Calendar.ZONE_OFFSET);
223 candidate.clear(Calendar.DST_OFFSET);
224 candidate.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
225 candidate.setMinimalDaysInFirstWeek(4);
226 candidate.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
227 }
228
229 if (frequency == NO_RECURRENCE) {
230 candidate.setTime(dtStart.getTime());
231
232 return candidate;
233 }
234
235 reduce_constant_length_field(Calendar.SECOND, dtStart, candidate);
236 reduce_constant_length_field(Calendar.MINUTE, dtStart, candidate);
237 reduce_constant_length_field(Calendar.HOUR_OF_DAY, dtStart, candidate);
238
239 switch (minInterval) {
240
241 case DAILY :
242
243
244
245 break;
246
247 case WEEKLY :
248 reduce_constant_length_field(Calendar.DAY_OF_WEEK, dtStart,
249 candidate);
250 break;
251
252 case MONTHLY :
253 reduce_day_of_month(dtStart, candidate);
254 break;
255
256 case YEARLY :
257 reduce_day_of_year(dtStart, candidate);
258 break;
259 }
260
261 return candidate;
262 }
263
264
269 public Calendar getDtEnd() {
270
271
275 Calendar tempEnd = (Calendar)dtStart.clone();
276
277 tempEnd.setTime(new Date(dtStart.getTime().getTime()
278 + duration.getInterval()));
279
280 return tempEnd;
281 }
282
283
288 public Calendar getDtStart() {
289 return (Calendar)dtStart.clone();
290 }
291
292
297 public Duration getDuration() {
298 return (Duration)duration.clone();
299 }
300
301
306 public int getFrequency() {
307 return frequency;
308 }
309
310
315 public int getInterval() {
316 return interval;
317 }
318
319
324 public int getOccurrence() {
325 return occurrence;
326 }
327
328
333 public Calendar getUntil() {
334 return ((until != null) ? (Calendar)until.clone() : null);
335 }
336
337
342 public int getWeekStart() {
343 return dtStart.getFirstDayOfWeek();
344 }
345
346
351 public boolean isInRecurrence(Calendar current) {
352 return isInRecurrence(current, false);
353 }
354
355
360 public boolean isInRecurrence(Calendar current, boolean debug) {
361 Calendar myCurrent = (Calendar)current.clone();
362
363
364
365 myCurrent.clear(Calendar.ZONE_OFFSET);
366 myCurrent.clear(Calendar.DST_OFFSET);
367 myCurrent.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
368 myCurrent.setMinimalDaysInFirstWeek(4);
369 myCurrent.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
370
371 if (myCurrent.getTime().getTime() < dtStart.getTime().getTime()) {
372
373
374
375 if (debug) {
376 System.err.println("current < start");
377 }
378
379 return false;
380 }
381
382 Calendar candidate = getCandidateStartTime(myCurrent);
383
384
385
386 while (candidate.getTime().getTime() + duration.getInterval()
387 > myCurrent.getTime().getTime()) {
388 if (candidateIsInRecurrence(candidate, debug)) {
389 return true;
390 }
391
392
393
394 candidate.add(Calendar.SECOND, -1);
395
396
397
398 if (candidate.getTime().getTime() < dtStart.getTime().getTime()) {
399 if (debug) {
400 System.err.println("No candidates after dtStart");
401 }
402
403 return false;
404 }
405
406 candidate = getCandidateStartTime(candidate);
407 }
408
409 if (debug) {
410 System.err.println("No matching candidates");
411 }
412
413 return false;
414 }
415
416
419 public void setByDay(DayAndPosition[] b) {
420 if (b == null) {
421 byDay = null;
422
423 return;
424 }
425
426 byDay = new DayAndPosition[b.length];
427
428
432 for (int i = 0; i < b.length; i++) {
433 byDay[i] = (DayAndPosition)b[i].clone();
434 }
435 }
436
437
440 public void setByMonth(int[] b) {
441 if (b == null) {
442 byMonth = null;
443
444 return;
445 }
446
447 byMonth = new int[b.length];
448
449 System.arraycopy(b, 0, byMonth, 0, b.length);
450 }
451
452
455 public void setByMonthDay(int[] b) {
456 if (b == null) {
457 byMonthDay = null;
458
459 return;
460 }
461
462 byMonthDay = new int[b.length];
463
464 System.arraycopy(b, 0, byMonthDay, 0, b.length);
465 }
466
467
470 public void setByWeekNo(int[] b) {
471 if (b == null) {
472 byWeekNo = null;
473
474 return;
475 }
476
477 byWeekNo = new int[b.length];
478
479 System.arraycopy(b, 0, byWeekNo, 0, b.length);
480 }
481
482
485 public void setByYearDay(int[] b) {
486 if (b == null) {
487 byYearDay = null;
488
489 return;
490 }
491
492 byYearDay = new int[b.length];
493
494 System.arraycopy(b, 0, byYearDay, 0, b.length);
495 }
496
497
500 public void setDtEnd(Calendar end) {
501 Calendar tempEnd = (Calendar)end.clone();
502
503 tempEnd.clear(Calendar.ZONE_OFFSET);
504 tempEnd.clear(Calendar.DST_OFFSET);
505 tempEnd.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
506 duration.setInterval(tempEnd.getTime().getTime()
507 - dtStart.getTime().getTime());
508 }
509
510
513 public void setDtStart(Calendar start) {
514 int oldStart;
515
516 if (dtStart != null) {
517 oldStart = dtStart.getFirstDayOfWeek();
518 }
519 else {
520 oldStart = Calendar.MONDAY;
521 }
522
523 if (start == null) {
524 dtStart = CalendarFactoryUtil.getCalendar(
525 TimeZoneUtil.getTimeZone(StringPool.UTC));
526
527 dtStart.setTime(new Date(0L));
528 }
529 else {
530 dtStart = (Calendar)start.clone();
531
532 dtStart.clear(Calendar.ZONE_OFFSET);
533 dtStart.clear(Calendar.DST_OFFSET);
534 dtStart.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
535 }
536
537 dtStart.setMinimalDaysInFirstWeek(4);
538 dtStart.setFirstDayOfWeek(oldStart);
539 }
540
541
544 public void setDuration(Duration d) {
545 duration = (Duration)d.clone();
546 }
547
548
551 public void setFrequency(int freq) {
552 if ((frequency != DAILY) && (frequency != WEEKLY)
553 && (frequency != MONTHLY) && (frequency != YEARLY)
554 && (frequency != NO_RECURRENCE)) {
555 throw new IllegalArgumentException("Invalid frequency");
556 }
557
558 frequency = freq;
559 }
560
561
564 public void setInterval(int intr) {
565 interval = (intr > 0) ? intr : 1;
566 }
567
568
571 public void setOccurrence(int occur) {
572 occurrence = occur;
573 }
574
575
578 public void setUntil(Calendar u) {
579 if (u == null) {
580 until = null;
581
582 return;
583 }
584
585 until = (Calendar)u.clone();
586
587 until.clear(Calendar.ZONE_OFFSET);
588 until.clear(Calendar.DST_OFFSET);
589 until.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
590 }
591
592
595 public void setWeekStart(int weekstart) {
596 dtStart.setFirstDayOfWeek(weekstart);
597 }
598
599
604 @Override
605 public String toString() {
606 StringBundler sb = new StringBundler();
607
608 sb.append(getClass().getName());
609 sb.append("[dtStart=");
610 sb.append((dtStart != null) ? dtStart.toString() : "null");
611 sb.append(",duration=");
612 sb.append((duration != null) ? duration.toString() : "null");
613 sb.append(",frequency=");
614 sb.append(frequency);
615 sb.append(",interval=");
616 sb.append(interval);
617 sb.append(",until=");
618 sb.append((until != null) ? until.toString() : "null");
619 sb.append(",byDay=");
620
621 if (byDay == null) {
622 sb.append("null");
623 }
624 else {
625 sb.append("[");
626
627 for (int i = 0; i < byDay.length; i++) {
628 if (i != 0) {
629 sb.append(",");
630 }
631
632 if (byDay[i] != null) {
633 sb.append(byDay[i].toString());
634 }
635 else {
636 sb.append("null");
637 }
638 }
639
640 sb.append("]");
641 }
642
643 sb.append(",byMonthDay=");
644 sb.append(stringizeIntArray(byMonthDay));
645 sb.append(",byYearDay=");
646 sb.append(stringizeIntArray(byYearDay));
647 sb.append(",byWeekNo=");
648 sb.append(stringizeIntArray(byWeekNo));
649 sb.append(",byMonth=");
650 sb.append(stringizeIntArray(byMonth));
651 sb.append(']');
652
653 return sb.toString();
654 }
655
656
661 protected static long getDayNumber(Calendar cal) {
662 Calendar tempCal = (Calendar)cal.clone();
663
664
665
666 tempCal.set(Calendar.MILLISECOND, 0);
667 tempCal.set(Calendar.SECOND, 0);
668 tempCal.set(Calendar.MINUTE, 0);
669 tempCal.set(Calendar.HOUR_OF_DAY, 0);
670
671 return tempCal.getTime().getTime() / (24 * 60 * 60 * 1000);
672 }
673
674
679 protected static long getMonthNumber(Calendar cal) {
680 return (cal.get(Calendar.YEAR) - 1970) * 12L
681 + (cal.get(Calendar.MONTH) - Calendar.JANUARY);
682 }
683
684
689 protected static long getWeekNumber(Calendar cal) {
690 Calendar tempCal = (Calendar)cal.clone();
691
692
693
694 tempCal.set(Calendar.MILLISECOND, 0);
695 tempCal.set(Calendar.SECOND, 0);
696 tempCal.set(Calendar.MINUTE, 0);
697 tempCal.set(Calendar.HOUR_OF_DAY, 0);
698
699
700
701 int delta = tempCal.getFirstDayOfWeek()
702 - tempCal.get(Calendar.DAY_OF_WEEK);
703
704 if (delta > 0) {
705 delta -= 7;
706 }
707
708
709
710
711
712
713 long weekEpoch = (tempCal.getFirstDayOfWeek() - Calendar.THURSDAY) * 24L
714 * 60 * 60 * 1000;
715
716 return (tempCal.getTime().getTime() - weekEpoch)
717 / (7 * 24 * 60 * 60 * 1000);
718 }
719
720
723 protected static void reduce_constant_length_field(int field,
724 Calendar start,
725 Calendar candidate) {
726 if ((start.getMaximum(field) != start.getLeastMaximum(field))
727 || (start.getMinimum(field) != start.getGreatestMinimum(field))) {
728 throw new IllegalArgumentException("Not a constant length field");
729 }
730
731 int fieldLength = (start.getMaximum(field) - start.getMinimum(field)
732 + 1);
733 int delta = start.get(field) - candidate.get(field);
734
735 if (delta > 0) {
736 delta -= fieldLength;
737 }
738
739 candidate.add(field, delta);
740 }
741
742
745 protected static void reduce_day_of_month(Calendar start,
746 Calendar candidate) {
747 Calendar tempCal = (Calendar)candidate.clone();
748
749 tempCal.add(Calendar.MONTH, -1);
750
751 int delta = start.get(Calendar.DATE) - candidate.get(Calendar.DATE);
752
753 if (delta > 0) {
754 delta -= tempCal.getActualMaximum(Calendar.DATE);
755 }
756
757 candidate.add(Calendar.DATE, delta);
758
759 while (start.get(Calendar.DATE) != candidate.get(Calendar.DATE)) {
760 tempCal.add(Calendar.MONTH, -1);
761 candidate.add(Calendar.DATE,
762 -tempCal.getActualMaximum(Calendar.DATE));
763 }
764 }
765
766
769 protected static void reduce_day_of_year(Calendar start,
770 Calendar candidate) {
771 if ((start.get(Calendar.MONTH) > candidate.get(Calendar.MONTH))
772 || ((start.get(Calendar.MONTH) == candidate.get(Calendar.MONTH))
773 && (start.get(Calendar.DATE) > candidate.get(Calendar.DATE)))) {
774 candidate.add(Calendar.YEAR, -1);
775 }
776
777
778
779 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
780 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
781
782 while ((start.get(Calendar.MONTH) != candidate.get(Calendar.MONTH))
783 || (start.get(Calendar.DATE) != candidate.get(Calendar.DATE))) {
784 candidate.add(Calendar.YEAR, -1);
785 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
786 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
787 }
788 }
789
790
795 protected boolean candidateIsInRecurrence(Calendar candidate,
796 boolean debug) {
797 if ((until != null)
798 && (candidate.getTime().getTime() > until.getTime().getTime())) {
799
800
801
802 if (debug) {
803 System.err.println("after until");
804 }
805
806 return false;
807 }
808
809 if (getRecurrenceCount(candidate) % interval != 0) {
810
811
812
813 if (debug) {
814 System.err.println("not an interval rep");
815 }
816
817 return false;
818 }
819 else if ((occurrence > 0) &&
820 (getRecurrenceCount(candidate) >= occurrence)) {
821
822 return false;
823 }
824
825 if (!matchesByDay(candidate) ||!matchesByMonthDay(candidate)
826 ||!matchesByYearDay(candidate) ||!matchesByWeekNo(candidate)
827 ||!matchesByMonth(candidate)) {
828
829
830
831 if (debug) {
832 System.err.println("doesn't match a by*");
833 }
834
835 return false;
836 }
837
838 if (debug) {
839 System.err.println("All checks succeeded");
840 }
841
842 return true;
843 }
844
845
850 protected int getMinimumInterval() {
851 if ((frequency == DAILY) || (byDay != null) || (byMonthDay != null)
852 || (byYearDay != null)) {
853 return DAILY;
854 }
855 else if ((frequency == WEEKLY) || (byWeekNo != null)) {
856 return WEEKLY;
857 }
858 else if ((frequency == MONTHLY) || (byMonth != null)) {
859 return MONTHLY;
860 }
861 else if (frequency == YEARLY) {
862 return YEARLY;
863 }
864 else if (frequency == NO_RECURRENCE) {
865 return NO_RECURRENCE;
866 }
867 else {
868
869
870
871 throw new IllegalStateException(
872 "Internal error: Unknown frequency value");
873 }
874 }
875
876
881 protected int getRecurrenceCount(Calendar candidate) {
882 switch (frequency) {
883
884 case NO_RECURRENCE :
885 return 0;
886
887 case DAILY :
888 return (int)(getDayNumber(candidate) - getDayNumber(dtStart));
889
890 case WEEKLY :
891 Calendar tempCand = (Calendar)candidate.clone();
892
893 tempCand.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
894
895 return (int)(getWeekNumber(tempCand) - getWeekNumber(dtStart));
896
897 case MONTHLY :
898 return (int)(getMonthNumber(candidate)
899 - getMonthNumber(dtStart));
900
901 case YEARLY :
902 return candidate.get(Calendar.YEAR)
903 - dtStart.get(Calendar.YEAR);
904
905 default :
906 throw new IllegalStateException("bad frequency internally...");
907 }
908 }
909
910
915 protected boolean matchesByDay(Calendar candidate) {
916 if ((byDay == null) || (byDay.length == 0)) {
917
918
919
920 return true;
921 }
922
923 int i;
924
925 for (i = 0; i < byDay.length; i++) {
926 if (matchesIndividualByDay(candidate, byDay[i])) {
927 return true;
928 }
929 }
930
931 return false;
932 }
933
934
939 protected boolean matchesByField(int[] array, int field,
940 Calendar candidate,
941 boolean allowNegative) {
942 if ((array == null) || (array.length == 0)) {
943
944
945
946 return true;
947 }
948
949 int i;
950
951 for (i = 0; i < array.length; i++) {
952 int val;
953
954 if (allowNegative && (array[i] < 0)) {
955
956
957
958 int max = candidate.getActualMaximum(field);
959
960 val = (max + 1) + array[i];
961 }
962 else {
963 val = array[i];
964 }
965
966 if (val == candidate.get(field)) {
967 return true;
968 }
969 }
970
971 return false;
972 }
973
974
979 protected boolean matchesByMonth(Calendar candidate) {
980 return matchesByField(byMonth, Calendar.MONTH, candidate, false);
981 }
982
983
988 protected boolean matchesByMonthDay(Calendar candidate) {
989 return matchesByField(byMonthDay, Calendar.DATE, candidate, true);
990 }
991
992
997 protected boolean matchesByWeekNo(Calendar candidate) {
998 return matchesByField(byWeekNo, Calendar.WEEK_OF_YEAR, candidate, true);
999 }
1000
1001
1006 protected boolean matchesByYearDay(Calendar candidate) {
1007 return matchesByField(byYearDay, Calendar.DAY_OF_YEAR, candidate, true);
1008 }
1009
1010
1015 protected boolean matchesIndividualByDay(Calendar candidate,
1016 DayAndPosition pos) {
1017 if (pos.getDayOfWeek() != candidate.get(Calendar.DAY_OF_WEEK)) {
1018 return false;
1019 }
1020
1021 int position = pos.getDayPosition();
1022
1023 if (position == 0) {
1024 return true;
1025 }
1026
1027 int field = Calendar.DAY_OF_MONTH;
1028
1029 if (position > 0) {
1030 int candidatePosition = ((candidate.get(field) - 1) / 7) + 1;
1031
1032 return (position == candidatePosition);
1033 }
1034 else {
1035
1036
1037
1038 int negativeCandidatePosition =
1039 ((candidate.getActualMaximum(field) - candidate.get(field)) / 7)
1040 + 1;
1041
1042 return (-position == negativeCandidatePosition);
1043 }
1044 }
1045
1046
1051 protected String stringizeIntArray(int[] a) {
1052 if (a == null) {
1053 return "null";
1054 }
1055
1056 StringBundler sb = new StringBundler(2 * a.length + 1);
1057
1058 sb.append("[");
1059
1060 for (int i = 0; i < a.length; i++) {
1061 if (i != 0) {
1062 sb.append(",");
1063 }
1064
1065 sb.append(a[i]);
1066 }
1067
1068 sb.append("]");
1069
1070 return sb.toString();
1071 }
1072
1073
1076 protected DayAndPosition[] byDay;
1077
1078
1081 protected int[] byMonth;
1082
1083
1086 protected int[] byMonthDay;
1087
1088
1091 protected int[] byWeekNo;
1092
1093
1096 protected int[] byYearDay;
1097
1098
1101 protected Calendar dtStart;
1102
1103
1106 protected Duration duration;
1107
1108
1111 protected int frequency;
1112
1113
1116 protected int interval;
1117
1118
1121 protected int occurrence = 0;
1122
1123
1126 protected Calendar until;
1127
1128 }