1
30
31 package com.liferay.portal.kernel.cal;
32
33 import com.liferay.portal.kernel.util.CalendarFactoryUtil;
34 import com.liferay.portal.kernel.util.StringMaker;
35
36 import java.io.Serializable;
37
38 import java.util.Calendar;
39 import java.util.Date;
40 import java.util.TimeZone;
41
42
48 public class Recurrence implements Serializable {
49
50
53 public final static int DAILY = 3;
54
55
58 public final static int WEEKLY = 4;
59
60
63 public final static int MONTHLY = 5;
64
65
68 public final static int YEARLY = 6;
69
70
73 public final static int NO_RECURRENCE = 7;
74
75
78 protected Calendar dtStart;
79
80
83 protected Duration duration;
84
85
88 protected int frequency;
89
90
93 protected int interval;
94
95
98 protected int occurrence = 0;
99
100
103 protected Calendar until;
104
105
108 protected DayAndPosition[] byDay;
109
110
113 protected int[] byMonthDay;
114
115
118 protected int[] byYearDay;
119
120
123 protected int[] byWeekNo;
124
125
128 protected int[] byMonth;
129
130
135 public Recurrence() {
136 this(null, new Duration(), NO_RECURRENCE);
137 }
138
139
147 public Recurrence(Calendar start, Duration dur) {
148 this(start, dur, NO_RECURRENCE);
149 }
150
151
160 public Recurrence(Calendar start, Duration dur, int freq) {
161 setDtStart(start);
162
163 duration = (Duration)dur.clone();
164 frequency = freq;
165 interval = 1;
166 }
167
168
169
170
177 public Calendar getDtStart() {
178 return (Calendar)dtStart.clone();
179 }
180
181
188 public void setDtStart(Calendar start) {
189 int oldStart;
190
191 if (dtStart != null) {
192 oldStart = dtStart.getFirstDayOfWeek();
193 }
194 else {
195 oldStart = Calendar.MONDAY;
196 }
197
198 if (start == null) {
199 dtStart = CalendarFactoryUtil.getCalendar(
200 TimeZone.getTimeZone("GMT"));
201
202 dtStart.setTime(new Date(0L));
203 }
204 else {
205 dtStart = (Calendar)start.clone();
206
207 dtStart.clear(Calendar.ZONE_OFFSET);
208 dtStart.clear(Calendar.DST_OFFSET);
209 dtStart.setTimeZone(TimeZone.getTimeZone("GMT"));
210 }
211
212 dtStart.setMinimalDaysInFirstWeek(4);
213 dtStart.setFirstDayOfWeek(oldStart);
214 }
215
216
223 public Duration getDuration() {
224 return (Duration)duration.clone();
225 }
226
227
234 public void setDuration(Duration d) {
235 duration = (Duration)d.clone();
236 }
237
238
245 public Calendar getDtEnd() {
246
247
251 Calendar tempEnd = (Calendar)dtStart.clone();
252
253 tempEnd.setTime(new Date(dtStart.getTime().getTime()
254 + duration.getInterval()));
255
256 return tempEnd;
257 }
258
259
266 public void setDtEnd(Calendar end) {
267 Calendar tempEnd = (Calendar)end.clone();
268
269 tempEnd.clear(Calendar.ZONE_OFFSET);
270 tempEnd.clear(Calendar.DST_OFFSET);
271 tempEnd.setTimeZone(TimeZone.getTimeZone("GMT"));
272 duration.setInterval(tempEnd.getTime().getTime()
273 - dtStart.getTime().getTime());
274 }
275
276
283 public int getFrequency() {
284 return frequency;
285 }
286
287
294 public void setFrequency(int freq) {
295 if ((frequency != DAILY) && (frequency != WEEKLY)
296 && (frequency != MONTHLY) && (frequency != YEARLY)
297 && (frequency != NO_RECURRENCE)) {
298 throw new IllegalArgumentException("Invalid frequency");
299 }
300
301 frequency = freq;
302 }
303
304
311 public int getInterval() {
312 return interval;
313 }
314
315
322 public void setInterval(int intr) {
323 interval = intr;
324 }
325
326
333 public int getOccurrence() {
334 return occurrence;
335 }
336
337
344 public void setOccurrence(int occur) {
345 occurrence = occur;
346 }
347
348
355 public Calendar getUntil() {
356 return ((until != null) ? (Calendar)until.clone() : null);
357 }
358
359
366 public void setUntil(Calendar u) {
367 if (u == null) {
368 until = null;
369
370 return;
371 }
372
373 until = (Calendar)u.clone();
374
375 until.clear(Calendar.ZONE_OFFSET);
376 until.clear(Calendar.DST_OFFSET);
377 until.setTimeZone(TimeZone.getTimeZone("GMT"));
378 }
379
380
387 public int getWeekStart() {
388 return dtStart.getFirstDayOfWeek();
389 }
390
391
398 public void setWeekStart(int weekstart) {
399 dtStart.setFirstDayOfWeek(weekstart);
400 }
401
402
409 public DayAndPosition[] getByDay() {
410 if (byDay == null) {
411 return null;
412 }
413
414 DayAndPosition[] b = new DayAndPosition[byDay.length];
415
416
420 for (int i = 0; i < byDay.length; i++) {
421 b[i] = (DayAndPosition)byDay[i].clone();
422 }
423
424 return b;
425 }
426
427
434 public void setByDay(DayAndPosition[] b) {
435 if (b == null) {
436 byDay = null;
437
438 return;
439 }
440
441 byDay = new DayAndPosition[b.length];
442
443
447 for (int i = 0; i < b.length; i++) {
448 byDay[i] = (DayAndPosition)b[i].clone();
449 }
450 }
451
452
459 public int[] getByMonthDay() {
460 if (byMonthDay == null) {
461 return null;
462 }
463
464 int[] b = new int[byMonthDay.length];
465
466 System.arraycopy(byMonthDay, 0, b, 0, byMonthDay.length);
467
468 return b;
469 }
470
471
478 public void setByMonthDay(int[] b) {
479 if (b == null) {
480 byMonthDay = null;
481
482 return;
483 }
484
485 byMonthDay = new int[b.length];
486
487 System.arraycopy(b, 0, byMonthDay, 0, b.length);
488 }
489
490
497 public int[] getByYearDay() {
498 if (byYearDay == null) {
499 return null;
500 }
501
502 int[] b = new int[byYearDay.length];
503
504 System.arraycopy(byYearDay, 0, b, 0, byYearDay.length);
505
506 return b;
507 }
508
509
516 public void setByYearDay(int[] b) {
517 if (b == null) {
518 byYearDay = null;
519
520 return;
521 }
522
523 byYearDay = new int[b.length];
524
525 System.arraycopy(b, 0, byYearDay, 0, b.length);
526 }
527
528
535 public int[] getByWeekNo() {
536 if (byWeekNo == null) {
537 return null;
538 }
539
540 int[] b = new int[byWeekNo.length];
541
542 System.arraycopy(byWeekNo, 0, b, 0, byWeekNo.length);
543
544 return b;
545 }
546
547
554 public void setByWeekNo(int[] b) {
555 if (b == null) {
556 byWeekNo = null;
557
558 return;
559 }
560
561 byWeekNo = new int[b.length];
562
563 System.arraycopy(b, 0, byWeekNo, 0, b.length);
564 }
565
566
573 public int[] getByMonth() {
574 if (byMonth == null) {
575 return null;
576 }
577
578 int[] b = new int[byMonth.length];
579
580 System.arraycopy(byMonth, 0, b, 0, byMonth.length);
581
582 return b;
583 }
584
585
592 public void setByMonth(int[] b) {
593 if (b == null) {
594 byMonth = null;
595
596 return;
597 }
598
599 byMonth = new int[b.length];
600
601 System.arraycopy(b, 0, byMonth, 0, b.length);
602 }
603
604
613 public boolean isInRecurrence(Calendar current) {
614 return isInRecurrence(current, false);
615 }
616
617
627 public boolean isInRecurrence(Calendar current, boolean debug) {
628 Calendar myCurrent = (Calendar)current.clone();
629
630
632 myCurrent.clear(Calendar.ZONE_OFFSET);
633 myCurrent.clear(Calendar.DST_OFFSET);
634 myCurrent.setTimeZone(TimeZone.getTimeZone("GMT"));
635 myCurrent.setMinimalDaysInFirstWeek(4);
636 myCurrent.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
637
638 if (myCurrent.getTime().getTime() < dtStart.getTime().getTime()) {
639
640
642 if (debug) {
643 System.err.println("current < start");
644 }
645
646 return false;
647 }
648
649 if (myCurrent.getTime().getTime()
650 < dtStart.getTime().getTime() + duration.getInterval()) {
651
652
654 if (debug) {
655 System.err.println("within duration of start");
656 }
657
658 return true;
659 }
660
661 Calendar candidate = getCandidateStartTime(myCurrent);
662
663
664
665 while (candidate.getTime().getTime() + duration.getInterval()
666 > myCurrent.getTime().getTime()) {
667 if (candidateIsInRecurrence(candidate, debug)) {
668 return true;
669 }
670
671
672
673 candidate.add(Calendar.SECOND, -1);
674
675
676
677 if (candidate.getTime().getTime() < dtStart.getTime().getTime()) {
678 if (debug) {
679 System.err.println("No candidates after dtStart");
680 }
681
682 return false;
683 }
684
685 candidate = getCandidateStartTime(candidate);
686 }
687
688 if (debug) {
689 System.err.println("No matching candidates");
690 }
691
692 return false;
693 }
694
695
705 protected boolean candidateIsInRecurrence(Calendar candidate,
706 boolean debug) {
707 if ((until != null)
708 && (candidate.getTime().getTime() > until.getTime().getTime())) {
709
710
712 if (debug) {
713 System.err.println("after until");
714 }
715
716 return false;
717 }
718
719 if (getRecurrenceCount(candidate) % interval != 0) {
720
721
723 if (debug) {
724 System.err.println("not an interval rep");
725 }
726
727 return false;
728 }
729 else if ((occurrence > 0) &&
730 (getRecurrenceCount(candidate) >= occurrence)) {
731
732 return false;
733 }
734
735 if (!matchesByDay(candidate) ||!matchesByMonthDay(candidate)
736 ||!matchesByYearDay(candidate) ||!matchesByWeekNo(candidate)
737 ||!matchesByMonth(candidate)) {
738
739
741 if (debug) {
742 System.err.println("doesn't match a by*");
743 }
744
745 return false;
746 }
747
748 if (debug) {
749 System.err.println("All checks succeeded");
750 }
751
752 return true;
753 }
754
755
762 protected int getMinimumInterval() {
763 if ((frequency == DAILY) || (byDay != null) || (byMonthDay != null)
764 || (byYearDay != null)) {
765 return DAILY;
766 }
767 else if ((frequency == WEEKLY) || (byWeekNo != null)) {
768 return WEEKLY;
769 }
770 else if ((frequency == MONTHLY) || (byMonth != null)) {
771 return MONTHLY;
772 }
773 else if (frequency == YEARLY) {
774 return YEARLY;
775 }
776 else if (frequency == NO_RECURRENCE) {
777 return NO_RECURRENCE;
778 }
779 else {
780
781
783 throw new IllegalStateException(
784 "Internal error: Unknown frequency value");
785 }
786 }
787
788
797 public Calendar getCandidateStartTime(Calendar current) {
798 if (dtStart.getTime().getTime() > current.getTime().getTime()) {
799 throw new IllegalArgumentException("Current time before DtStart");
800 }
801
802 int minInterval = getMinimumInterval();
803 Calendar candidate = (Calendar)current.clone();
804
805 if (true) {
806
807
809 candidate.clear(Calendar.ZONE_OFFSET);
810 candidate.clear(Calendar.DST_OFFSET);
811 candidate.setTimeZone(TimeZone.getTimeZone("GMT"));
812 candidate.setMinimalDaysInFirstWeek(4);
813 candidate.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
814 }
815
816 if (frequency == NO_RECURRENCE) {
817 candidate.setTime(dtStart.getTime());
818
819 return candidate;
820 }
821
822 reduce_constant_length_field(Calendar.SECOND, dtStart, candidate);
823 reduce_constant_length_field(Calendar.MINUTE, dtStart, candidate);
824 reduce_constant_length_field(Calendar.HOUR_OF_DAY, dtStart, candidate);
825
826 switch (minInterval) {
827
828 case DAILY :
829
830
831
832 break;
833
834 case WEEKLY :
835 reduce_constant_length_field(Calendar.DAY_OF_WEEK, dtStart,
836 candidate);
837 break;
838
839 case MONTHLY :
840 reduce_day_of_month(dtStart, candidate);
841 break;
842
843 case YEARLY :
844 reduce_day_of_year(dtStart, candidate);
845 break;
846 }
847
848 return candidate;
849 }
850
851
860 protected static void reduce_constant_length_field(int field,
861 Calendar start,
862 Calendar candidate) {
863 if ((start.getMaximum(field) != start.getLeastMaximum(field))
864 || (start.getMinimum(field) != start.getGreatestMinimum(field))) {
865 throw new IllegalArgumentException("Not a constant length field");
866 }
867
868 int fieldLength = (start.getMaximum(field) - start.getMinimum(field)
869 + 1);
870 int delta = start.get(field) - candidate.get(field);
871
872 if (delta > 0) {
873 delta -= fieldLength;
874 }
875
876 candidate.add(field, delta);
877 }
878
879
887 protected static void reduce_day_of_month(Calendar start,
888 Calendar candidate) {
889 Calendar tempCal = (Calendar)candidate.clone();
890
891 tempCal.add(Calendar.MONTH, -1);
892
893 int delta = start.get(Calendar.DATE) - candidate.get(Calendar.DATE);
894
895 if (delta > 0) {
896 delta -= tempCal.getActualMaximum(Calendar.DATE);
897 }
898
899 candidate.add(Calendar.DATE, delta);
900
901 while (start.get(Calendar.DATE) != candidate.get(Calendar.DATE)) {
902 tempCal.add(Calendar.MONTH, -1);
903 candidate.add(Calendar.DATE,
904 -tempCal.getActualMaximum(Calendar.DATE));
905 }
906 }
907
908
916 protected static void reduce_day_of_year(Calendar start,
917 Calendar candidate) {
918 if ((start.get(Calendar.MONTH) > candidate.get(Calendar.MONTH))
919 || ((start.get(Calendar.MONTH) == candidate.get(Calendar.MONTH))
920 && (start.get(Calendar.DATE) > candidate.get(Calendar.DATE)))) {
921 candidate.add(Calendar.YEAR, -1);
922 }
923
924
925
926 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
927 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
928
929 while ((start.get(Calendar.MONTH) != candidate.get(Calendar.MONTH))
930 || (start.get(Calendar.DATE) != candidate.get(Calendar.DATE))) {
931 candidate.add(Calendar.YEAR, -1);
932 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
933 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
934 }
935 }
936
937
946 protected int getRecurrenceCount(Calendar candidate) {
947 switch (frequency) {
948
949 case NO_RECURRENCE :
950 return 0;
951
952 case DAILY :
953 return (int)(getDayNumber(candidate) - getDayNumber(dtStart));
954
955 case WEEKLY :
956 Calendar tempCand = (Calendar)candidate.clone();
957
958 tempCand.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
959
960 return (int)(getWeekNumber(tempCand) - getWeekNumber(dtStart));
961
962 case MONTHLY :
963 return (int)(getMonthNumber(candidate)
964 - getMonthNumber(dtStart));
965
966 case YEARLY :
967 return candidate.get(Calendar.YEAR)
968 - dtStart.get(Calendar.YEAR);
969
970 default :
971 throw new IllegalStateException("bad frequency internally...");
972 }
973 }
974
975
984 protected static long getDayNumber(Calendar cal) {
985 Calendar tempCal = (Calendar)cal.clone();
986
987
989 tempCal.set(Calendar.MILLISECOND, 0);
990 tempCal.set(Calendar.SECOND, 0);
991 tempCal.set(Calendar.MINUTE, 0);
992 tempCal.set(Calendar.HOUR_OF_DAY, 0);
993
994 return tempCal.getTime().getTime() / (24 * 60 * 60 * 1000);
995 }
996
997
1006 protected static long getWeekNumber(Calendar cal) {
1007 Calendar tempCal = (Calendar)cal.clone();
1008
1009
1011 tempCal.set(Calendar.MILLISECOND, 0);
1012 tempCal.set(Calendar.SECOND, 0);
1013 tempCal.set(Calendar.MINUTE, 0);
1014 tempCal.set(Calendar.HOUR_OF_DAY, 0);
1015
1016
1018 int delta = tempCal.getFirstDayOfWeek()
1019 - tempCal.get(Calendar.DAY_OF_WEEK);
1020
1021 if (delta > 0) {
1022 delta -= 7;
1023 }
1024
1025
1027
1030 long weekEpoch = (tempCal.getFirstDayOfWeek() - Calendar.THURSDAY) * 24
1031 * 60 * 60 * 1000L;
1032
1033 return (tempCal.getTime().getTime() - weekEpoch)
1034 / (7 * 24 * 60 * 60 * 1000);
1035 }
1036
1037
1046 protected static long getMonthNumber(Calendar cal) {
1047 return (cal.get(Calendar.YEAR) - 1970) * 12
1048 + (cal.get(Calendar.MONTH) - Calendar.JANUARY);
1049 }
1050
1051
1060 protected boolean matchesByDay(Calendar candidate) {
1061 if ((byDay == null) || (byDay.length == 0)) {
1062
1063
1064
1065 return true;
1066 }
1067
1068 int i;
1069
1070 for (i = 0; i < byDay.length; i++) {
1071 if (matchesIndividualByDay(candidate, byDay[i])) {
1072 return true;
1073 }
1074 }
1075
1076 return false;
1077 }
1078
1079
1089 protected boolean matchesIndividualByDay(Calendar candidate,
1090 DayAndPosition pos) {
1091 if (pos.getDayOfWeek() != candidate.get(Calendar.DAY_OF_WEEK)) {
1092 return false;
1093 }
1094
1095 int position = pos.getDayPosition();
1096
1097 if (position == 0) {
1098 return true;
1099 }
1100
1101 int field;
1102
1103 switch (frequency) {
1104
1105 case MONTHLY :
1106 field = Calendar.DAY_OF_MONTH;
1107 break;
1108
1109 case YEARLY :
1110 field = Calendar.DAY_OF_YEAR;
1111 break;
1112
1113 default :
1114 throw new IllegalStateException(
1115 "byday has a day position "
1116 + "in non-MONTHLY or YEARLY recurrence");
1117 }
1118
1119 if (position > 0) {
1120 int day_of_week_in_field = ((candidate.get(field) - 1) / 7) + 1;
1121
1122 return (position == day_of_week_in_field);
1123 }
1124 else {
1125
1126
1127
1128 int negative_day_of_week_in_field =
1129 ((candidate.getActualMaximum(field) - candidate.get(field)) / 7)
1130 + 1;
1131
1132 return (-position == negative_day_of_week_in_field);
1133 }
1134 }
1135
1136
1148 protected static boolean matchesByField(int[] array, int field,
1149 Calendar candidate,
1150 boolean allowNegative) {
1151 if ((array == null) || (array.length == 0)) {
1152
1153
1154
1155 return true;
1156 }
1157
1158 int i;
1159
1160 for (i = 0; i < array.length; i++) {
1161 int val;
1162
1163 if (allowNegative && (array[i] < 0)) {
1164
1165
1167 int max = candidate.getActualMaximum(field);
1168
1169 val = (max + 1) + array[i];
1170 }
1171 else {
1172 val = array[i];
1173 }
1174
1175 if (val == candidate.get(field)) {
1176 return true;
1177 }
1178 }
1179
1180 return false;
1181 }
1182
1183
1192 protected boolean matchesByMonthDay(Calendar candidate) {
1193 return matchesByField(byMonthDay, Calendar.DATE, candidate, true);
1194 }
1195
1196
1205 protected boolean matchesByYearDay(Calendar candidate) {
1206 return matchesByField(byYearDay, Calendar.DAY_OF_YEAR, candidate, true);
1207 }
1208
1209
1218 protected boolean matchesByWeekNo(Calendar candidate) {
1219 return matchesByField(byWeekNo, Calendar.WEEK_OF_YEAR, candidate, true);
1220 }
1221
1222
1231 protected boolean matchesByMonth(Calendar candidate) {
1232 return matchesByField(byMonth, Calendar.MONTH, candidate, false);
1233 }
1234
1235
1242 public String toString() {
1243 StringMaker sm = new StringMaker();
1244
1245 sm.append(getClass().getName());
1246 sm.append("[dtStart=");
1247 sm.append((dtStart != null) ? dtStart.toString() : "null");
1248 sm.append(",duration=");
1249 sm.append((duration != null) ? duration.toString() : "null");
1250 sm.append(",frequency=");
1251 sm.append(frequency);
1252 sm.append(",interval=");
1253 sm.append(interval);
1254 sm.append(",until=");
1255 sm.append((until != null) ? until.toString() : "null");
1256 sm.append(",byDay=");
1257
1258 if (byDay == null) {
1259 sm.append("null");
1260 }
1261 else {
1262 sm.append("[");
1263
1264 for (int i = 0; i < byDay.length; i++) {
1265 if (i != 0) {
1266 sm.append(",");
1267 }
1268
1269 if (byDay[i] != null) {
1270 sm.append(byDay[i].toString());
1271 }
1272 else {
1273 sm.append("null");
1274 }
1275 }
1276
1277 sm.append("]");
1278 }
1279
1280 sm.append(",byMonthDay=");
1281 sm.append(stringizeIntArray(byMonthDay));
1282 sm.append(",byYearDay=");
1283 sm.append(stringizeIntArray(byYearDay));
1284 sm.append(",byWeekNo=");
1285 sm.append(stringizeIntArray(byWeekNo));
1286 sm.append(",byMonth=");
1287 sm.append(stringizeIntArray(byMonth));
1288 sm.append(']');
1289
1290 return sm.toString();
1291 }
1292
1293
1302 private String stringizeIntArray(int[] a) {
1303 if (a == null) {
1304 return "null";
1305 }
1306
1307 StringMaker sm = new StringMaker();
1308
1309 sm.append("[");
1310
1311 for (int i = 0; i < a.length; i++) {
1312 if (i != 0) {
1313 sm.append(",");
1314 }
1315
1316 sm.append(a[i]);
1317 }
1318
1319 sm.append("]");
1320
1321 return sm.toString();
1322 }
1323
1324}