1
22
23
52
53 package com.liferay.portal.kernel.cal;
54
55 import com.liferay.portal.kernel.util.CalendarFactoryUtil;
56
57 import java.io.Serializable;
58
59 import java.util.Calendar;
60 import java.util.Date;
61 import java.util.TimeZone;
62
63
69 public class Recurrence implements Serializable {
70
71
74 public final static int DAILY = 3;
75
76
79 public final static int WEEKLY = 4;
80
81
84 public final static int MONTHLY = 5;
85
86
89 public final static int YEARLY = 6;
90
91
94 public final static int NO_RECURRENCE = 7;
95
96
99 protected Calendar dtStart;
100
101
104 protected Duration duration;
105
106
109 protected int frequency;
110
111
114 protected int interval;
115
116
119 protected int occurrence = 0;
120
121
124 protected Calendar until;
125
126
129 protected DayAndPosition[] byDay;
130
131
134 protected int[] byMonthDay;
135
136
139 protected int[] byYearDay;
140
141
144 protected int[] byWeekNo;
145
146
149 protected int[] byMonth;
150
151
156 public Recurrence() {
157 this(null, new Duration(), NO_RECURRENCE);
158 }
159
160
168 public Recurrence(Calendar start, Duration dur) {
169 this(start, dur, NO_RECURRENCE);
170 }
171
172
181 public Recurrence(Calendar start, Duration dur, int freq) {
182 setDtStart(start);
183
184 duration = (Duration)dur.clone();
185 frequency = freq;
186 interval = 1;
187 }
188
189
190
191
198 public Calendar getDtStart() {
199 return (Calendar)dtStart.clone();
200 }
201
202
209 public void setDtStart(Calendar start) {
210 int oldStart;
211
212 if (dtStart != null) {
213 oldStart = dtStart.getFirstDayOfWeek();
214 }
215 else {
216 oldStart = Calendar.MONDAY;
217 }
218
219 if (start == null) {
220 dtStart = CalendarFactoryUtil.getCalendar(
221 TimeZone.getTimeZone("GMT"));
222
223 dtStart.setTime(new Date(0L));
224 }
225 else {
226 dtStart = (Calendar)start.clone();
227
228 dtStart.clear(Calendar.ZONE_OFFSET);
229 dtStart.clear(Calendar.DST_OFFSET);
230 dtStart.setTimeZone(TimeZone.getTimeZone("GMT"));
231 }
232
233 dtStart.setMinimalDaysInFirstWeek(4);
234 dtStart.setFirstDayOfWeek(oldStart);
235 }
236
237
244 public Duration getDuration() {
245 return (Duration)duration.clone();
246 }
247
248
255 public void setDuration(Duration d) {
256 duration = (Duration)d.clone();
257 }
258
259
266 public Calendar getDtEnd() {
267
268
272 Calendar tempEnd = (Calendar)dtStart.clone();
273
274 tempEnd.setTime(new Date(dtStart.getTime().getTime()
275 + duration.getInterval()));
276
277 return tempEnd;
278 }
279
280
287 public void setDtEnd(Calendar end) {
288 Calendar tempEnd = (Calendar)end.clone();
289
290 tempEnd.clear(Calendar.ZONE_OFFSET);
291 tempEnd.clear(Calendar.DST_OFFSET);
292 tempEnd.setTimeZone(TimeZone.getTimeZone("GMT"));
293 duration.setInterval(tempEnd.getTime().getTime()
294 - dtStart.getTime().getTime());
295 }
296
297
304 public int getFrequency() {
305 return frequency;
306 }
307
308
315 public void setFrequency(int freq) {
316 if ((frequency != DAILY) && (frequency != WEEKLY)
317 && (frequency != MONTHLY) && (frequency != YEARLY)
318 && (frequency != NO_RECURRENCE)) {
319 throw new IllegalArgumentException("Invalid frequency");
320 }
321
322 frequency = freq;
323 }
324
325
332 public int getInterval() {
333 return interval;
334 }
335
336
343 public void setInterval(int intr) {
344 interval = (intr > 0) ? intr : 1;
345 }
346
347
354 public int getOccurrence() {
355 return occurrence;
356 }
357
358
365 public void setOccurrence(int occur) {
366 occurrence = occur;
367 }
368
369
376 public Calendar getUntil() {
377 return ((until != null) ? (Calendar)until.clone() : null);
378 }
379
380
387 public void setUntil(Calendar u) {
388 if (u == null) {
389 until = null;
390
391 return;
392 }
393
394 until = (Calendar)u.clone();
395
396 until.clear(Calendar.ZONE_OFFSET);
397 until.clear(Calendar.DST_OFFSET);
398 until.setTimeZone(TimeZone.getTimeZone("GMT"));
399 }
400
401
408 public int getWeekStart() {
409 return dtStart.getFirstDayOfWeek();
410 }
411
412
419 public void setWeekStart(int weekstart) {
420 dtStart.setFirstDayOfWeek(weekstart);
421 }
422
423
430 public DayAndPosition[] getByDay() {
431 if (byDay == null) {
432 return null;
433 }
434
435 DayAndPosition[] b = new DayAndPosition[byDay.length];
436
437
441 for (int i = 0; i < byDay.length; i++) {
442 b[i] = (DayAndPosition)byDay[i].clone();
443 }
444
445 return b;
446 }
447
448
455 public void setByDay(DayAndPosition[] b) {
456 if (b == null) {
457 byDay = null;
458
459 return;
460 }
461
462 byDay = new DayAndPosition[b.length];
463
464
468 for (int i = 0; i < b.length; i++) {
469 byDay[i] = (DayAndPosition)b[i].clone();
470 }
471 }
472
473
480 public int[] getByMonthDay() {
481 if (byMonthDay == null) {
482 return null;
483 }
484
485 int[] b = new int[byMonthDay.length];
486
487 System.arraycopy(byMonthDay, 0, b, 0, byMonthDay.length);
488
489 return b;
490 }
491
492
499 public void setByMonthDay(int[] b) {
500 if (b == null) {
501 byMonthDay = null;
502
503 return;
504 }
505
506 byMonthDay = new int[b.length];
507
508 System.arraycopy(b, 0, byMonthDay, 0, b.length);
509 }
510
511
518 public int[] getByYearDay() {
519 if (byYearDay == null) {
520 return null;
521 }
522
523 int[] b = new int[byYearDay.length];
524
525 System.arraycopy(byYearDay, 0, b, 0, byYearDay.length);
526
527 return b;
528 }
529
530
537 public void setByYearDay(int[] b) {
538 if (b == null) {
539 byYearDay = null;
540
541 return;
542 }
543
544 byYearDay = new int[b.length];
545
546 System.arraycopy(b, 0, byYearDay, 0, b.length);
547 }
548
549
556 public int[] getByWeekNo() {
557 if (byWeekNo == null) {
558 return null;
559 }
560
561 int[] b = new int[byWeekNo.length];
562
563 System.arraycopy(byWeekNo, 0, b, 0, byWeekNo.length);
564
565 return b;
566 }
567
568
575 public void setByWeekNo(int[] b) {
576 if (b == null) {
577 byWeekNo = null;
578
579 return;
580 }
581
582 byWeekNo = new int[b.length];
583
584 System.arraycopy(b, 0, byWeekNo, 0, b.length);
585 }
586
587
594 public int[] getByMonth() {
595 if (byMonth == null) {
596 return null;
597 }
598
599 int[] b = new int[byMonth.length];
600
601 System.arraycopy(byMonth, 0, b, 0, byMonth.length);
602
603 return b;
604 }
605
606
613 public void setByMonth(int[] b) {
614 if (b == null) {
615 byMonth = null;
616
617 return;
618 }
619
620 byMonth = new int[b.length];
621
622 System.arraycopy(b, 0, byMonth, 0, b.length);
623 }
624
625
634 public boolean isInRecurrence(Calendar current) {
635 return isInRecurrence(current, false);
636 }
637
638
648 public boolean isInRecurrence(Calendar current, boolean debug) {
649 Calendar myCurrent = (Calendar)current.clone();
650
651
653 myCurrent.clear(Calendar.ZONE_OFFSET);
654 myCurrent.clear(Calendar.DST_OFFSET);
655 myCurrent.setTimeZone(TimeZone.getTimeZone("GMT"));
656 myCurrent.setMinimalDaysInFirstWeek(4);
657 myCurrent.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
658
659 if (myCurrent.getTime().getTime() < dtStart.getTime().getTime()) {
660
661
663 if (debug) {
664 System.err.println("current < start");
665 }
666
667 return false;
668 }
669
670 if (myCurrent.getTime().getTime()
671 < dtStart.getTime().getTime() + duration.getInterval()) {
672
673
675 if (debug) {
676 System.err.println("within duration of start");
677 }
678
679 return true;
680 }
681
682 Calendar candidate = getCandidateStartTime(myCurrent);
683
684
685
686 while (candidate.getTime().getTime() + duration.getInterval()
687 > myCurrent.getTime().getTime()) {
688 if (candidateIsInRecurrence(candidate, debug)) {
689 return true;
690 }
691
692
693
694 candidate.add(Calendar.SECOND, -1);
695
696
697
698 if (candidate.getTime().getTime() < dtStart.getTime().getTime()) {
699 if (debug) {
700 System.err.println("No candidates after dtStart");
701 }
702
703 return false;
704 }
705
706 candidate = getCandidateStartTime(candidate);
707 }
708
709 if (debug) {
710 System.err.println("No matching candidates");
711 }
712
713 return false;
714 }
715
716
726 protected boolean candidateIsInRecurrence(Calendar candidate,
727 boolean debug) {
728 if ((until != null)
729 && (candidate.getTime().getTime() > until.getTime().getTime())) {
730
731
733 if (debug) {
734 System.err.println("after until");
735 }
736
737 return false;
738 }
739
740 if (getRecurrenceCount(candidate) % interval != 0) {
741
742
744 if (debug) {
745 System.err.println("not an interval rep");
746 }
747
748 return false;
749 }
750 else if ((occurrence > 0) &&
751 (getRecurrenceCount(candidate) >= occurrence)) {
752
753 return false;
754 }
755
756 if (!matchesByDay(candidate) ||!matchesByMonthDay(candidate)
757 ||!matchesByYearDay(candidate) ||!matchesByWeekNo(candidate)
758 ||!matchesByMonth(candidate)) {
759
760
762 if (debug) {
763 System.err.println("doesn't match a by*");
764 }
765
766 return false;
767 }
768
769 if (debug) {
770 System.err.println("All checks succeeded");
771 }
772
773 return true;
774 }
775
776
783 protected int getMinimumInterval() {
784 if ((frequency == DAILY) || (byDay != null) || (byMonthDay != null)
785 || (byYearDay != null)) {
786 return DAILY;
787 }
788 else if ((frequency == WEEKLY) || (byWeekNo != null)) {
789 return WEEKLY;
790 }
791 else if ((frequency == MONTHLY) || (byMonth != null)) {
792 return MONTHLY;
793 }
794 else if (frequency == YEARLY) {
795 return YEARLY;
796 }
797 else if (frequency == NO_RECURRENCE) {
798 return NO_RECURRENCE;
799 }
800 else {
801
802
804 throw new IllegalStateException(
805 "Internal error: Unknown frequency value");
806 }
807 }
808
809
818 public Calendar getCandidateStartTime(Calendar current) {
819 if (dtStart.getTime().getTime() > current.getTime().getTime()) {
820 throw new IllegalArgumentException("Current time before DtStart");
821 }
822
823 int minInterval = getMinimumInterval();
824 Calendar candidate = (Calendar)current.clone();
825
826 if (true) {
827
828
830 candidate.clear(Calendar.ZONE_OFFSET);
831 candidate.clear(Calendar.DST_OFFSET);
832 candidate.setTimeZone(TimeZone.getTimeZone("GMT"));
833 candidate.setMinimalDaysInFirstWeek(4);
834 candidate.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
835 }
836
837 if (frequency == NO_RECURRENCE) {
838 candidate.setTime(dtStart.getTime());
839
840 return candidate;
841 }
842
843 reduce_constant_length_field(Calendar.SECOND, dtStart, candidate);
844 reduce_constant_length_field(Calendar.MINUTE, dtStart, candidate);
845 reduce_constant_length_field(Calendar.HOUR_OF_DAY, dtStart, candidate);
846
847 switch (minInterval) {
848
849 case DAILY :
850
851
852
853 break;
854
855 case WEEKLY :
856 reduce_constant_length_field(Calendar.DAY_OF_WEEK, dtStart,
857 candidate);
858 break;
859
860 case MONTHLY :
861 reduce_day_of_month(dtStart, candidate);
862 break;
863
864 case YEARLY :
865 reduce_day_of_year(dtStart, candidate);
866 break;
867 }
868
869 return candidate;
870 }
871
872
881 protected static void reduce_constant_length_field(int field,
882 Calendar start,
883 Calendar candidate) {
884 if ((start.getMaximum(field) != start.getLeastMaximum(field))
885 || (start.getMinimum(field) != start.getGreatestMinimum(field))) {
886 throw new IllegalArgumentException("Not a constant length field");
887 }
888
889 int fieldLength = (start.getMaximum(field) - start.getMinimum(field)
890 + 1);
891 int delta = start.get(field) - candidate.get(field);
892
893 if (delta > 0) {
894 delta -= fieldLength;
895 }
896
897 candidate.add(field, delta);
898 }
899
900
908 protected static void reduce_day_of_month(Calendar start,
909 Calendar candidate) {
910 Calendar tempCal = (Calendar)candidate.clone();
911
912 tempCal.add(Calendar.MONTH, -1);
913
914 int delta = start.get(Calendar.DATE) - candidate.get(Calendar.DATE);
915
916 if (delta > 0) {
917 delta -= tempCal.getActualMaximum(Calendar.DATE);
918 }
919
920 candidate.add(Calendar.DATE, delta);
921
922 while (start.get(Calendar.DATE) != candidate.get(Calendar.DATE)) {
923 tempCal.add(Calendar.MONTH, -1);
924 candidate.add(Calendar.DATE,
925 -tempCal.getActualMaximum(Calendar.DATE));
926 }
927 }
928
929
937 protected static void reduce_day_of_year(Calendar start,
938 Calendar candidate) {
939 if ((start.get(Calendar.MONTH) > candidate.get(Calendar.MONTH))
940 || ((start.get(Calendar.MONTH) == candidate.get(Calendar.MONTH))
941 && (start.get(Calendar.DATE) > candidate.get(Calendar.DATE)))) {
942 candidate.add(Calendar.YEAR, -1);
943 }
944
945
946
947 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
948 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
949
950 while ((start.get(Calendar.MONTH) != candidate.get(Calendar.MONTH))
951 || (start.get(Calendar.DATE) != candidate.get(Calendar.DATE))) {
952 candidate.add(Calendar.YEAR, -1);
953 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
954 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
955 }
956 }
957
958
967 protected int getRecurrenceCount(Calendar candidate) {
968 switch (frequency) {
969
970 case NO_RECURRENCE :
971 return 0;
972
973 case DAILY :
974 return (int)(getDayNumber(candidate) - getDayNumber(dtStart));
975
976 case WEEKLY :
977 Calendar tempCand = (Calendar)candidate.clone();
978
979 tempCand.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
980
981 return (int)(getWeekNumber(tempCand) - getWeekNumber(dtStart));
982
983 case MONTHLY :
984 return (int)(getMonthNumber(candidate)
985 - getMonthNumber(dtStart));
986
987 case YEARLY :
988 return candidate.get(Calendar.YEAR)
989 - dtStart.get(Calendar.YEAR);
990
991 default :
992 throw new IllegalStateException("bad frequency internally...");
993 }
994 }
995
996
1005 protected static long getDayNumber(Calendar cal) {
1006 Calendar tempCal = (Calendar)cal.clone();
1007
1008
1010 tempCal.set(Calendar.MILLISECOND, 0);
1011 tempCal.set(Calendar.SECOND, 0);
1012 tempCal.set(Calendar.MINUTE, 0);
1013 tempCal.set(Calendar.HOUR_OF_DAY, 0);
1014
1015 return tempCal.getTime().getTime() / (24 * 60 * 60 * 1000);
1016 }
1017
1018
1027 protected static long getWeekNumber(Calendar cal) {
1028 Calendar tempCal = (Calendar)cal.clone();
1029
1030
1032 tempCal.set(Calendar.MILLISECOND, 0);
1033 tempCal.set(Calendar.SECOND, 0);
1034 tempCal.set(Calendar.MINUTE, 0);
1035 tempCal.set(Calendar.HOUR_OF_DAY, 0);
1036
1037
1039 int delta = tempCal.getFirstDayOfWeek()
1040 - tempCal.get(Calendar.DAY_OF_WEEK);
1041
1042 if (delta > 0) {
1043 delta -= 7;
1044 }
1045
1046
1048
1051 long weekEpoch = (tempCal.getFirstDayOfWeek() - Calendar.THURSDAY) * 24
1052 * 60 * 60 * 1000L;
1053
1054 return (tempCal.getTime().getTime() - weekEpoch)
1055 / (7 * 24 * 60 * 60 * 1000);
1056 }
1057
1058
1067 protected static long getMonthNumber(Calendar cal) {
1068 return (cal.get(Calendar.YEAR) - 1970) * 12
1069 + (cal.get(Calendar.MONTH) - Calendar.JANUARY);
1070 }
1071
1072
1081 protected boolean matchesByDay(Calendar candidate) {
1082 if ((byDay == null) || (byDay.length == 0)) {
1083
1084
1085
1086 return true;
1087 }
1088
1089 int i;
1090
1091 for (i = 0; i < byDay.length; i++) {
1092 if (matchesIndividualByDay(candidate, byDay[i])) {
1093 return true;
1094 }
1095 }
1096
1097 return false;
1098 }
1099
1100
1110 protected boolean matchesIndividualByDay(Calendar candidate,
1111 DayAndPosition pos) {
1112 if (pos.getDayOfWeek() != candidate.get(Calendar.DAY_OF_WEEK)) {
1113 return false;
1114 }
1115
1116 int position = pos.getDayPosition();
1117
1118 if (position == 0) {
1119 return true;
1120 }
1121
1122 int field;
1123
1124 switch (frequency) {
1125
1126 case MONTHLY :
1127 field = Calendar.DAY_OF_MONTH;
1128 break;
1129
1130 case YEARLY :
1131 field = Calendar.DAY_OF_YEAR;
1132 break;
1133
1134 default :
1135 throw new IllegalStateException(
1136 "byday has a day position "
1137 + "in non-MONTHLY or YEARLY recurrence");
1138 }
1139
1140 if (position > 0) {
1141 int day_of_week_in_field = ((candidate.get(field) - 1) / 7) + 1;
1142
1143 return (position == day_of_week_in_field);
1144 }
1145 else {
1146
1147
1148
1149 int negative_day_of_week_in_field =
1150 ((candidate.getActualMaximum(field) - candidate.get(field)) / 7)
1151 + 1;
1152
1153 return (-position == negative_day_of_week_in_field);
1154 }
1155 }
1156
1157
1169 protected static boolean matchesByField(int[] array, int field,
1170 Calendar candidate,
1171 boolean allowNegative) {
1172 if ((array == null) || (array.length == 0)) {
1173
1174
1175
1176 return true;
1177 }
1178
1179 int i;
1180
1181 for (i = 0; i < array.length; i++) {
1182 int val;
1183
1184 if (allowNegative && (array[i] < 0)) {
1185
1186
1188 int max = candidate.getActualMaximum(field);
1189
1190 val = (max + 1) + array[i];
1191 }
1192 else {
1193 val = array[i];
1194 }
1195
1196 if (val == candidate.get(field)) {
1197 return true;
1198 }
1199 }
1200
1201 return false;
1202 }
1203
1204
1213 protected boolean matchesByMonthDay(Calendar candidate) {
1214 return matchesByField(byMonthDay, Calendar.DATE, candidate, true);
1215 }
1216
1217
1226 protected boolean matchesByYearDay(Calendar candidate) {
1227 return matchesByField(byYearDay, Calendar.DAY_OF_YEAR, candidate, true);
1228 }
1229
1230
1239 protected boolean matchesByWeekNo(Calendar candidate) {
1240 return matchesByField(byWeekNo, Calendar.WEEK_OF_YEAR, candidate, true);
1241 }
1242
1243
1252 protected boolean matchesByMonth(Calendar candidate) {
1253 return matchesByField(byMonth, Calendar.MONTH, candidate, false);
1254 }
1255
1256
1263 public String toString() {
1264 StringBuilder sb = new StringBuilder();
1265
1266 sb.append(getClass().getName());
1267 sb.append("[dtStart=");
1268 sb.append((dtStart != null) ? dtStart.toString() : "null");
1269 sb.append(",duration=");
1270 sb.append((duration != null) ? duration.toString() : "null");
1271 sb.append(",frequency=");
1272 sb.append(frequency);
1273 sb.append(",interval=");
1274 sb.append(interval);
1275 sb.append(",until=");
1276 sb.append((until != null) ? until.toString() : "null");
1277 sb.append(",byDay=");
1278
1279 if (byDay == null) {
1280 sb.append("null");
1281 }
1282 else {
1283 sb.append("[");
1284
1285 for (int i = 0; i < byDay.length; i++) {
1286 if (i != 0) {
1287 sb.append(",");
1288 }
1289
1290 if (byDay[i] != null) {
1291 sb.append(byDay[i].toString());
1292 }
1293 else {
1294 sb.append("null");
1295 }
1296 }
1297
1298 sb.append("]");
1299 }
1300
1301 sb.append(",byMonthDay=");
1302 sb.append(stringizeIntArray(byMonthDay));
1303 sb.append(",byYearDay=");
1304 sb.append(stringizeIntArray(byYearDay));
1305 sb.append(",byWeekNo=");
1306 sb.append(stringizeIntArray(byWeekNo));
1307 sb.append(",byMonth=");
1308 sb.append(stringizeIntArray(byMonth));
1309 sb.append(']');
1310
1311 return sb.toString();
1312 }
1313
1314
1323 private String stringizeIntArray(int[] a) {
1324 if (a == null) {
1325 return "null";
1326 }
1327
1328 StringBuilder sb = new StringBuilder();
1329
1330 sb.append("[");
1331
1332 for (int i = 0; i < a.length; i++) {
1333 if (i != 0) {
1334 sb.append(",");
1335 }
1336
1337 sb.append(a[i]);
1338 }
1339
1340 sb.append("]");
1341
1342 return sb.toString();
1343 }
1344
1345}