1
19
20
49
50 package com.liferay.portal.kernel.cal;
51
52 import com.liferay.portal.kernel.util.CalendarFactoryUtil;
53 import com.liferay.portal.kernel.util.StringPool;
54
55 import java.io.Serializable;
56
57 import java.util.Calendar;
58 import java.util.Date;
59 import java.util.TimeZone;
60
61
67 public class Recurrence implements Serializable {
68
69
72 public final static int DAILY = 3;
73
74
77 public final static int WEEKLY = 4;
78
79
82 public final static int MONTHLY = 5;
83
84
87 public final static int YEARLY = 6;
88
89
92 public final static int NO_RECURRENCE = 7;
93
94
97 protected Calendar dtStart;
98
99
102 protected Duration duration;
103
104
107 protected int frequency;
108
109
112 protected int interval;
113
114
117 protected int occurrence = 0;
118
119
122 protected Calendar until;
123
124
127 protected DayAndPosition[] byDay;
128
129
132 protected int[] byMonthDay;
133
134
137 protected int[] byYearDay;
138
139
142 protected int[] byWeekNo;
143
144
147 protected int[] byMonth;
148
149
154 public Recurrence() {
155 this(null, new Duration(), NO_RECURRENCE);
156 }
157
158
166 public Recurrence(Calendar start, Duration dur) {
167 this(start, dur, NO_RECURRENCE);
168 }
169
170
179 public Recurrence(Calendar start, Duration dur, int freq) {
180 setDtStart(start);
181
182 duration = (Duration)dur.clone();
183 frequency = freq;
184 interval = 1;
185 }
186
187
188
189
196 public Calendar getDtStart() {
197 return (Calendar)dtStart.clone();
198 }
199
200
207 public void setDtStart(Calendar start) {
208 int oldStart;
209
210 if (dtStart != null) {
211 oldStart = dtStart.getFirstDayOfWeek();
212 }
213 else {
214 oldStart = Calendar.MONDAY;
215 }
216
217 if (start == null) {
218 dtStart = CalendarFactoryUtil.getCalendar(
219 TimeZone.getTimeZone(StringPool.UTC));
220
221 dtStart.setTime(new Date(0L));
222 }
223 else {
224 dtStart = (Calendar)start.clone();
225
226 dtStart.clear(Calendar.ZONE_OFFSET);
227 dtStart.clear(Calendar.DST_OFFSET);
228 dtStart.setTimeZone(TimeZone.getTimeZone(StringPool.UTC));
229 }
230
231 dtStart.setMinimalDaysInFirstWeek(4);
232 dtStart.setFirstDayOfWeek(oldStart);
233 }
234
235
242 public Duration getDuration() {
243 return (Duration)duration.clone();
244 }
245
246
253 public void setDuration(Duration d) {
254 duration = (Duration)d.clone();
255 }
256
257
264 public Calendar getDtEnd() {
265
266
270 Calendar tempEnd = (Calendar)dtStart.clone();
271
272 tempEnd.setTime(new Date(dtStart.getTime().getTime()
273 + duration.getInterval()));
274
275 return tempEnd;
276 }
277
278
285 public void setDtEnd(Calendar end) {
286 Calendar tempEnd = (Calendar)end.clone();
287
288 tempEnd.clear(Calendar.ZONE_OFFSET);
289 tempEnd.clear(Calendar.DST_OFFSET);
290 tempEnd.setTimeZone(TimeZone.getTimeZone(StringPool.UTC));
291 duration.setInterval(tempEnd.getTime().getTime()
292 - dtStart.getTime().getTime());
293 }
294
295
302 public int getFrequency() {
303 return frequency;
304 }
305
306
313 public void setFrequency(int freq) {
314 if ((frequency != DAILY) && (frequency != WEEKLY)
315 && (frequency != MONTHLY) && (frequency != YEARLY)
316 && (frequency != NO_RECURRENCE)) {
317 throw new IllegalArgumentException("Invalid frequency");
318 }
319
320 frequency = freq;
321 }
322
323
330 public int getInterval() {
331 return interval;
332 }
333
334
341 public void setInterval(int intr) {
342 interval = (intr > 0) ? intr : 1;
343 }
344
345
352 public int getOccurrence() {
353 return occurrence;
354 }
355
356
363 public void setOccurrence(int occur) {
364 occurrence = occur;
365 }
366
367
374 public Calendar getUntil() {
375 return ((until != null) ? (Calendar)until.clone() : null);
376 }
377
378
385 public void setUntil(Calendar u) {
386 if (u == null) {
387 until = null;
388
389 return;
390 }
391
392 until = (Calendar)u.clone();
393
394 until.clear(Calendar.ZONE_OFFSET);
395 until.clear(Calendar.DST_OFFSET);
396 until.setTimeZone(TimeZone.getTimeZone(StringPool.UTC));
397 }
398
399
406 public int getWeekStart() {
407 return dtStart.getFirstDayOfWeek();
408 }
409
410
417 public void setWeekStart(int weekstart) {
418 dtStart.setFirstDayOfWeek(weekstart);
419 }
420
421
428 public DayAndPosition[] getByDay() {
429 if (byDay == null) {
430 return null;
431 }
432
433 DayAndPosition[] b = new DayAndPosition[byDay.length];
434
435
439 for (int i = 0; i < byDay.length; i++) {
440 b[i] = (DayAndPosition)byDay[i].clone();
441 }
442
443 return b;
444 }
445
446
453 public void setByDay(DayAndPosition[] b) {
454 if (b == null) {
455 byDay = null;
456
457 return;
458 }
459
460 byDay = new DayAndPosition[b.length];
461
462
466 for (int i = 0; i < b.length; i++) {
467 byDay[i] = (DayAndPosition)b[i].clone();
468 }
469 }
470
471
478 public int[] getByMonthDay() {
479 if (byMonthDay == null) {
480 return null;
481 }
482
483 int[] b = new int[byMonthDay.length];
484
485 System.arraycopy(byMonthDay, 0, b, 0, byMonthDay.length);
486
487 return b;
488 }
489
490
497 public void setByMonthDay(int[] b) {
498 if (b == null) {
499 byMonthDay = null;
500
501 return;
502 }
503
504 byMonthDay = new int[b.length];
505
506 System.arraycopy(b, 0, byMonthDay, 0, b.length);
507 }
508
509
516 public int[] getByYearDay() {
517 if (byYearDay == null) {
518 return null;
519 }
520
521 int[] b = new int[byYearDay.length];
522
523 System.arraycopy(byYearDay, 0, b, 0, byYearDay.length);
524
525 return b;
526 }
527
528
535 public void setByYearDay(int[] b) {
536 if (b == null) {
537 byYearDay = null;
538
539 return;
540 }
541
542 byYearDay = new int[b.length];
543
544 System.arraycopy(b, 0, byYearDay, 0, b.length);
545 }
546
547
554 public int[] getByWeekNo() {
555 if (byWeekNo == null) {
556 return null;
557 }
558
559 int[] b = new int[byWeekNo.length];
560
561 System.arraycopy(byWeekNo, 0, b, 0, byWeekNo.length);
562
563 return b;
564 }
565
566
573 public void setByWeekNo(int[] b) {
574 if (b == null) {
575 byWeekNo = null;
576
577 return;
578 }
579
580 byWeekNo = new int[b.length];
581
582 System.arraycopy(b, 0, byWeekNo, 0, b.length);
583 }
584
585
592 public int[] getByMonth() {
593 if (byMonth == null) {
594 return null;
595 }
596
597 int[] b = new int[byMonth.length];
598
599 System.arraycopy(byMonth, 0, b, 0, byMonth.length);
600
601 return b;
602 }
603
604
611 public void setByMonth(int[] b) {
612 if (b == null) {
613 byMonth = null;
614
615 return;
616 }
617
618 byMonth = new int[b.length];
619
620 System.arraycopy(b, 0, byMonth, 0, b.length);
621 }
622
623
632 public boolean isInRecurrence(Calendar current) {
633 return isInRecurrence(current, false);
634 }
635
636
646 public boolean isInRecurrence(Calendar current, boolean debug) {
647 Calendar myCurrent = (Calendar)current.clone();
648
649
651 myCurrent.clear(Calendar.ZONE_OFFSET);
652 myCurrent.clear(Calendar.DST_OFFSET);
653 myCurrent.setTimeZone(TimeZone.getTimeZone(StringPool.UTC));
654 myCurrent.setMinimalDaysInFirstWeek(4);
655 myCurrent.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
656
657 if (myCurrent.getTime().getTime() < dtStart.getTime().getTime()) {
658
659
661 if (debug) {
662 System.err.println("current < start");
663 }
664
665 return false;
666 }
667
668 if (myCurrent.getTime().getTime()
669 < dtStart.getTime().getTime() + duration.getInterval()) {
670
671
673 if (debug) {
674 System.err.println("within duration of start");
675 }
676
677 return true;
678 }
679
680 Calendar candidate = getCandidateStartTime(myCurrent);
681
682
683
684 while (candidate.getTime().getTime() + duration.getInterval()
685 > myCurrent.getTime().getTime()) {
686 if (candidateIsInRecurrence(candidate, debug)) {
687 return true;
688 }
689
690
691
692 candidate.add(Calendar.SECOND, -1);
693
694
695
696 if (candidate.getTime().getTime() < dtStart.getTime().getTime()) {
697 if (debug) {
698 System.err.println("No candidates after dtStart");
699 }
700
701 return false;
702 }
703
704 candidate = getCandidateStartTime(candidate);
705 }
706
707 if (debug) {
708 System.err.println("No matching candidates");
709 }
710
711 return false;
712 }
713
714
724 protected boolean candidateIsInRecurrence(Calendar candidate,
725 boolean debug) {
726 if ((until != null)
727 && (candidate.getTime().getTime() > until.getTime().getTime())) {
728
729
731 if (debug) {
732 System.err.println("after until");
733 }
734
735 return false;
736 }
737
738 if (getRecurrenceCount(candidate) % interval != 0) {
739
740
742 if (debug) {
743 System.err.println("not an interval rep");
744 }
745
746 return false;
747 }
748 else if ((occurrence > 0) &&
749 (getRecurrenceCount(candidate) >= occurrence)) {
750
751 return false;
752 }
753
754 if (!matchesByDay(candidate) ||!matchesByMonthDay(candidate)
755 ||!matchesByYearDay(candidate) ||!matchesByWeekNo(candidate)
756 ||!matchesByMonth(candidate)) {
757
758
760 if (debug) {
761 System.err.println("doesn't match a by*");
762 }
763
764 return false;
765 }
766
767 if (debug) {
768 System.err.println("All checks succeeded");
769 }
770
771 return true;
772 }
773
774
781 protected int getMinimumInterval() {
782 if ((frequency == DAILY) || (byDay != null) || (byMonthDay != null)
783 || (byYearDay != null)) {
784 return DAILY;
785 }
786 else if ((frequency == WEEKLY) || (byWeekNo != null)) {
787 return WEEKLY;
788 }
789 else if ((frequency == MONTHLY) || (byMonth != null)) {
790 return MONTHLY;
791 }
792 else if (frequency == YEARLY) {
793 return YEARLY;
794 }
795 else if (frequency == NO_RECURRENCE) {
796 return NO_RECURRENCE;
797 }
798 else {
799
800
802 throw new IllegalStateException(
803 "Internal error: Unknown frequency value");
804 }
805 }
806
807
816 public Calendar getCandidateStartTime(Calendar current) {
817 if (dtStart.getTime().getTime() > current.getTime().getTime()) {
818 throw new IllegalArgumentException("Current time before DtStart");
819 }
820
821 int minInterval = getMinimumInterval();
822 Calendar candidate = (Calendar)current.clone();
823
824 if (true) {
825
826
828 candidate.clear(Calendar.ZONE_OFFSET);
829 candidate.clear(Calendar.DST_OFFSET);
830 candidate.setTimeZone(TimeZone.getTimeZone(StringPool.UTC));
831 candidate.setMinimalDaysInFirstWeek(4);
832 candidate.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
833 }
834
835 if (frequency == NO_RECURRENCE) {
836 candidate.setTime(dtStart.getTime());
837
838 return candidate;
839 }
840
841 reduce_constant_length_field(Calendar.SECOND, dtStart, candidate);
842 reduce_constant_length_field(Calendar.MINUTE, dtStart, candidate);
843 reduce_constant_length_field(Calendar.HOUR_OF_DAY, dtStart, candidate);
844
845 switch (minInterval) {
846
847 case DAILY :
848
849
850
851 break;
852
853 case WEEKLY :
854 reduce_constant_length_field(Calendar.DAY_OF_WEEK, dtStart,
855 candidate);
856 break;
857
858 case MONTHLY :
859 reduce_day_of_month(dtStart, candidate);
860 break;
861
862 case YEARLY :
863 reduce_day_of_year(dtStart, candidate);
864 break;
865 }
866
867 return candidate;
868 }
869
870
879 protected static void reduce_constant_length_field(int field,
880 Calendar start,
881 Calendar candidate) {
882 if ((start.getMaximum(field) != start.getLeastMaximum(field))
883 || (start.getMinimum(field) != start.getGreatestMinimum(field))) {
884 throw new IllegalArgumentException("Not a constant length field");
885 }
886
887 int fieldLength = (start.getMaximum(field) - start.getMinimum(field)
888 + 1);
889 int delta = start.get(field) - candidate.get(field);
890
891 if (delta > 0) {
892 delta -= fieldLength;
893 }
894
895 candidate.add(field, delta);
896 }
897
898
906 protected static void reduce_day_of_month(Calendar start,
907 Calendar candidate) {
908 Calendar tempCal = (Calendar)candidate.clone();
909
910 tempCal.add(Calendar.MONTH, -1);
911
912 int delta = start.get(Calendar.DATE) - candidate.get(Calendar.DATE);
913
914 if (delta > 0) {
915 delta -= tempCal.getActualMaximum(Calendar.DATE);
916 }
917
918 candidate.add(Calendar.DATE, delta);
919
920 while (start.get(Calendar.DATE) != candidate.get(Calendar.DATE)) {
921 tempCal.add(Calendar.MONTH, -1);
922 candidate.add(Calendar.DATE,
923 -tempCal.getActualMaximum(Calendar.DATE));
924 }
925 }
926
927
935 protected static void reduce_day_of_year(Calendar start,
936 Calendar candidate) {
937 if ((start.get(Calendar.MONTH) > candidate.get(Calendar.MONTH))
938 || ((start.get(Calendar.MONTH) == candidate.get(Calendar.MONTH))
939 && (start.get(Calendar.DATE) > candidate.get(Calendar.DATE)))) {
940 candidate.add(Calendar.YEAR, -1);
941 }
942
943
944
945 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
946 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
947
948 while ((start.get(Calendar.MONTH) != candidate.get(Calendar.MONTH))
949 || (start.get(Calendar.DATE) != candidate.get(Calendar.DATE))) {
950 candidate.add(Calendar.YEAR, -1);
951 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
952 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
953 }
954 }
955
956
965 protected int getRecurrenceCount(Calendar candidate) {
966 switch (frequency) {
967
968 case NO_RECURRENCE :
969 return 0;
970
971 case DAILY :
972 return (int)(getDayNumber(candidate) - getDayNumber(dtStart));
973
974 case WEEKLY :
975 Calendar tempCand = (Calendar)candidate.clone();
976
977 tempCand.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
978
979 return (int)(getWeekNumber(tempCand) - getWeekNumber(dtStart));
980
981 case MONTHLY :
982 return (int)(getMonthNumber(candidate)
983 - getMonthNumber(dtStart));
984
985 case YEARLY :
986 return candidate.get(Calendar.YEAR)
987 - dtStart.get(Calendar.YEAR);
988
989 default :
990 throw new IllegalStateException("bad frequency internally...");
991 }
992 }
993
994
1003 protected static long getDayNumber(Calendar cal) {
1004 Calendar tempCal = (Calendar)cal.clone();
1005
1006
1008 tempCal.set(Calendar.MILLISECOND, 0);
1009 tempCal.set(Calendar.SECOND, 0);
1010 tempCal.set(Calendar.MINUTE, 0);
1011 tempCal.set(Calendar.HOUR_OF_DAY, 0);
1012
1013 return tempCal.getTime().getTime() / (24 * 60 * 60 * 1000);
1014 }
1015
1016
1025 protected static long getWeekNumber(Calendar cal) {
1026 Calendar tempCal = (Calendar)cal.clone();
1027
1028
1030 tempCal.set(Calendar.MILLISECOND, 0);
1031 tempCal.set(Calendar.SECOND, 0);
1032 tempCal.set(Calendar.MINUTE, 0);
1033 tempCal.set(Calendar.HOUR_OF_DAY, 0);
1034
1035
1037 int delta = tempCal.getFirstDayOfWeek()
1038 - tempCal.get(Calendar.DAY_OF_WEEK);
1039
1040 if (delta > 0) {
1041 delta -= 7;
1042 }
1043
1044
1046
1049 long weekEpoch = (tempCal.getFirstDayOfWeek() - Calendar.THURSDAY) * 24
1050 * 60 * 60 * 1000L;
1051
1052 return (tempCal.getTime().getTime() - weekEpoch)
1053 / (7 * 24 * 60 * 60 * 1000);
1054 }
1055
1056
1065 protected static long getMonthNumber(Calendar cal) {
1066 return (cal.get(Calendar.YEAR) - 1970) * 12
1067 + (cal.get(Calendar.MONTH) - Calendar.JANUARY);
1068 }
1069
1070
1079 protected boolean matchesByDay(Calendar candidate) {
1080 if ((byDay == null) || (byDay.length == 0)) {
1081
1082
1083
1084 return true;
1085 }
1086
1087 int i;
1088
1089 for (i = 0; i < byDay.length; i++) {
1090 if (matchesIndividualByDay(candidate, byDay[i])) {
1091 return true;
1092 }
1093 }
1094
1095 return false;
1096 }
1097
1098
1108 protected boolean matchesIndividualByDay(Calendar candidate,
1109 DayAndPosition pos) {
1110 if (pos.getDayOfWeek() != candidate.get(Calendar.DAY_OF_WEEK)) {
1111 return false;
1112 }
1113
1114 int position = pos.getDayPosition();
1115
1116 if (position == 0) {
1117 return true;
1118 }
1119
1120 int field;
1121
1122 switch (frequency) {
1123
1124 case MONTHLY :
1125 field = Calendar.DAY_OF_MONTH;
1126 break;
1127
1128 case YEARLY :
1129 field = Calendar.DAY_OF_YEAR;
1130 break;
1131
1132 default :
1133 throw new IllegalStateException(
1134 "byday has a day position "
1135 + "in non-MONTHLY or YEARLY recurrence");
1136 }
1137
1138 if (position > 0) {
1139 int day_of_week_in_field = ((candidate.get(field) - 1) / 7) + 1;
1140
1141 return (position == day_of_week_in_field);
1142 }
1143 else {
1144
1145
1146
1147 int negative_day_of_week_in_field =
1148 ((candidate.getActualMaximum(field) - candidate.get(field)) / 7)
1149 + 1;
1150
1151 return (-position == negative_day_of_week_in_field);
1152 }
1153 }
1154
1155
1167 protected static boolean matchesByField(int[] array, int field,
1168 Calendar candidate,
1169 boolean allowNegative) {
1170 if ((array == null) || (array.length == 0)) {
1171
1172
1173
1174 return true;
1175 }
1176
1177 int i;
1178
1179 for (i = 0; i < array.length; i++) {
1180 int val;
1181
1182 if (allowNegative && (array[i] < 0)) {
1183
1184
1186 int max = candidate.getActualMaximum(field);
1187
1188 val = (max + 1) + array[i];
1189 }
1190 else {
1191 val = array[i];
1192 }
1193
1194 if (val == candidate.get(field)) {
1195 return true;
1196 }
1197 }
1198
1199 return false;
1200 }
1201
1202
1211 protected boolean matchesByMonthDay(Calendar candidate) {
1212 return matchesByField(byMonthDay, Calendar.DATE, candidate, true);
1213 }
1214
1215
1224 protected boolean matchesByYearDay(Calendar candidate) {
1225 return matchesByField(byYearDay, Calendar.DAY_OF_YEAR, candidate, true);
1226 }
1227
1228
1237 protected boolean matchesByWeekNo(Calendar candidate) {
1238 return matchesByField(byWeekNo, Calendar.WEEK_OF_YEAR, candidate, true);
1239 }
1240
1241
1250 protected boolean matchesByMonth(Calendar candidate) {
1251 return matchesByField(byMonth, Calendar.MONTH, candidate, false);
1252 }
1253
1254
1261 public String toString() {
1262 StringBuilder sb = new StringBuilder();
1263
1264 sb.append(getClass().getName());
1265 sb.append("[dtStart=");
1266 sb.append((dtStart != null) ? dtStart.toString() : "null");
1267 sb.append(",duration=");
1268 sb.append((duration != null) ? duration.toString() : "null");
1269 sb.append(",frequency=");
1270 sb.append(frequency);
1271 sb.append(",interval=");
1272 sb.append(interval);
1273 sb.append(",until=");
1274 sb.append((until != null) ? until.toString() : "null");
1275 sb.append(",byDay=");
1276
1277 if (byDay == null) {
1278 sb.append("null");
1279 }
1280 else {
1281 sb.append("[");
1282
1283 for (int i = 0; i < byDay.length; i++) {
1284 if (i != 0) {
1285 sb.append(",");
1286 }
1287
1288 if (byDay[i] != null) {
1289 sb.append(byDay[i].toString());
1290 }
1291 else {
1292 sb.append("null");
1293 }
1294 }
1295
1296 sb.append("]");
1297 }
1298
1299 sb.append(",byMonthDay=");
1300 sb.append(stringizeIntArray(byMonthDay));
1301 sb.append(",byYearDay=");
1302 sb.append(stringizeIntArray(byYearDay));
1303 sb.append(",byWeekNo=");
1304 sb.append(stringizeIntArray(byWeekNo));
1305 sb.append(",byMonth=");
1306 sb.append(stringizeIntArray(byMonth));
1307 sb.append(']');
1308
1309 return sb.toString();
1310 }
1311
1312
1321 private String stringizeIntArray(int[] a) {
1322 if (a == null) {
1323 return "null";
1324 }
1325
1326 StringBuilder sb = new StringBuilder();
1327
1328 sb.append("[");
1329
1330 for (int i = 0; i < a.length; i++) {
1331 if (i != 0) {
1332 sb.append(",");
1333 }
1334
1335 sb.append(a[i]);
1336 }
1337
1338 sb.append("]");
1339
1340 return sb.toString();
1341 }
1342
1343}