| Recurrence.java |
1 /**
2 * Copyright (c) 2000-2008 Liferay, Inc. All rights reserved.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 /*
24 * Copyright (c) 2000, Columbia University. All rights reserved.
25 *
26 * Redistribution and use in source and binary forms, with or without
27 * modification, are permitted provided that the following conditions are met:
28 *
29 * 1. Redistributions of source code must retain the above copyright
30 * notice, this list of conditions and the following disclaimer.
31 *
32 * 2. Redistributions in binary form must reproduce the above copyright
33 * notice, this list of conditions and the following disclaimer in the
34 * documentation and/or other materials provided with the distribution.
35 *
36 * 3. Neither the name of the University nor the names of its contributors
37 * may be used to endorse or promote products derived from this software
38 * without specific prior written permission.
39 *
40 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
41 * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
42 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
43 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
44 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
45 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
46 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
47 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
48 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
49 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
50 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51 */
52
53 package com.liferay.util.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 /**
64 * <a href="Recurrence.java.html"><b><i>View Source</i></b></a>
65 *
66 * @author Jonathan Lennox
67 *
68 * @deprecated This class has been repackaged at
69 * <code>com.liferay.portal.kernel.cal</code>.
70 *
71 */
72 public class Recurrence implements Serializable {
73
74 /**
75 * Field DAILY
76 */
77 public final static int DAILY = 3;
78
79 /**
80 * Field WEEKLY
81 */
82 public final static int WEEKLY = 4;
83
84 /**
85 * Field MONTHLY
86 */
87 public final static int MONTHLY = 5;
88
89 /**
90 * Field YEARLY
91 */
92 public final static int YEARLY = 6;
93
94 /**
95 * Field NO_RECURRENCE
96 */
97 public final static int NO_RECURRENCE = 7;
98
99 /**
100 * Field dtStart
101 */
102 protected Calendar dtStart;
103
104 /**
105 * Field duration
106 */
107 protected Duration duration;
108
109 /**
110 * Field frequency
111 */
112 protected int frequency;
113
114 /**
115 * Field interval
116 */
117 protected int interval;
118
119 /**
120 * Field interval
121 */
122 protected int occurrence = 0;
123
124 /**
125 * Field until
126 */
127 protected Calendar until;
128
129 /**
130 * Field byDay
131 */
132 protected DayAndPosition[] byDay;
133
134 /**
135 * Field byMonthDay
136 */
137 protected int[] byMonthDay;
138
139 /**
140 * Field byYearDay
141 */
142 protected int[] byYearDay;
143
144 /**
145 * Field byWeekNo
146 */
147 protected int[] byWeekNo;
148
149 /**
150 * Field byMonth
151 */
152 protected int[] byMonth;
153
154 /**
155 * Constructor Recurrence
156 *
157 *
158 */
159 public Recurrence() {
160 this(null, new Duration(), NO_RECURRENCE);
161 }
162
163 /**
164 * Constructor Recurrence
165 *
166 *
167 * @param start
168 * @param dur
169 *
170 */
171 public Recurrence(Calendar start, Duration dur) {
172 this(start, dur, NO_RECURRENCE);
173 }
174
175 /**
176 * Constructor Recurrence
177 *
178 *
179 * @param start
180 * @param dur
181 * @param freq
182 *
183 */
184 public Recurrence(Calendar start, Duration dur, int freq) {
185 setDtStart(start);
186
187 duration = (Duration)dur.clone();
188 frequency = freq;
189 interval = 1;
190 }
191
192 /* Accessors */
193
194 /**
195 * Method getDtStart
196 *
197 *
198 * @return Calendar
199 *
200 */
201 public Calendar getDtStart() {
202 return (Calendar)dtStart.clone();
203 }
204
205 /**
206 * Method setDtStart
207 *
208 *
209 * @param start
210 *
211 */
212 public void setDtStart(Calendar start) {
213 int oldStart;
214
215 if (dtStart != null) {
216 oldStart = dtStart.getFirstDayOfWeek();
217 }
218 else {
219 oldStart = Calendar.MONDAY;
220 }
221
222 if (start == null) {
223 dtStart = CalendarFactoryUtil.getCalendar(
224 TimeZone.getTimeZone("GMT"));
225
226 dtStart.setTime(new Date(0L));
227 }
228 else {
229 dtStart = (Calendar)start.clone();
230
231 dtStart.clear(Calendar.ZONE_OFFSET);
232 dtStart.clear(Calendar.DST_OFFSET);
233 dtStart.setTimeZone(TimeZone.getTimeZone("GMT"));
234 }
235
236 dtStart.setMinimalDaysInFirstWeek(4);
237 dtStart.setFirstDayOfWeek(oldStart);
238 }
239
240 /**
241 * Method getDuration
242 *
243 *
244 * @return Duration
245 *
246 */
247 public Duration getDuration() {
248 return (Duration)duration.clone();
249 }
250
251 /**
252 * Method setDuration
253 *
254 *
255 * @param d
256 *
257 */
258 public void setDuration(Duration d) {
259 duration = (Duration)d.clone();
260 }
261
262 /**
263 * Method getDtEnd
264 *
265 *
266 * @return Calendar
267 *
268 */
269 public Calendar getDtEnd() {
270
271 /*
272 * Make dtEnd a cloned dtStart, so non-time fields of the Calendar
273 * are accurate.
274 */
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 /**
284 * Method setDtEnd
285 *
286 *
287 * @param end
288 *
289 */
290 public void setDtEnd(Calendar end) {
291 Calendar tempEnd = (Calendar)end.clone();
292
293 tempEnd.clear(Calendar.ZONE_OFFSET);
294 tempEnd.clear(Calendar.DST_OFFSET);
295 tempEnd.setTimeZone(TimeZone.getTimeZone("GMT"));
296 duration.setInterval(tempEnd.getTime().getTime()
297 - dtStart.getTime().getTime());
298 }
299
300 /**
301 * Method getFrequency
302 *
303 *
304 * @return int
305 *
306 */
307 public int getFrequency() {
308 return frequency;
309 }
310
311 /**
312 * Method setFrequency
313 *
314 *
315 * @param freq
316 *
317 */
318 public void setFrequency(int freq) {
319 if ((frequency != DAILY) && (frequency != WEEKLY)
320 && (frequency != MONTHLY) && (frequency != YEARLY)
321 && (frequency != NO_RECURRENCE)) {
322 throw new IllegalArgumentException("Invalid frequency");
323 }
324
325 frequency = freq;
326 }
327
328 /**
329 * Method getInterval
330 *
331 *
332 * @return int
333 *
334 */
335 public int getInterval() {
336 return interval;
337 }
338
339 /**
340 * Method setInterval
341 *
342 *
343 * @param intr
344 *
345 */
346 public void setInterval(int intr) {
347 interval = (intr > 0) ? intr : 1;
348 }
349
350 /**
351 * Method getOccurrence
352 *
353 *
354 * @return int
355 *
356 */
357 public int getOccurrence() {
358 return occurrence;
359 }
360
361 /**
362 * Method setOccurrence
363 *
364 *
365 * @param occur
366 *
367 */
368 public void setOccurrence(int occur) {
369 occurrence = occur;
370 }
371
372 /**
373 * Method getUntil
374 *
375 *
376 * @return Calendar
377 *
378 */
379 public Calendar getUntil() {
380 return ((until != null) ? (Calendar)until.clone() : null);
381 }
382
383 /**
384 * Method setUntil
385 *
386 *
387 * @param u
388 *
389 */
390 public void setUntil(Calendar u) {
391 if (u == null) {
392 until = null;
393
394 return;
395 }
396
397 until = (Calendar)u.clone();
398
399 until.clear(Calendar.ZONE_OFFSET);
400 until.clear(Calendar.DST_OFFSET);
401 until.setTimeZone(TimeZone.getTimeZone("GMT"));
402 }
403
404 /**
405 * Method getWeekStart
406 *
407 *
408 * @return int
409 *
410 */
411 public int getWeekStart() {
412 return dtStart.getFirstDayOfWeek();
413 }
414
415 /**
416 * Method setWeekStart
417 *
418 *
419 * @param weekstart
420 *
421 */
422 public void setWeekStart(int weekstart) {
423 dtStart.setFirstDayOfWeek(weekstart);
424 }
425
426 /**
427 * Method getByDay
428 *
429 *
430 * @return DayAndPosition[]
431 *
432 */
433 public DayAndPosition[] getByDay() {
434 if (byDay == null) {
435 return null;
436 }
437
438 DayAndPosition[] b = new DayAndPosition[byDay.length];
439
440 /*
441 * System.arraycopy isn't good enough -- we want to clone each
442 * individual element.
443 */
444 for (int i = 0; i < byDay.length; i++) {
445 b[i] = (DayAndPosition)byDay[i].clone();
446 }
447
448 return b;
449 }
450
451 /**
452 * Method setByDay
453 *
454 *
455 * @param b
456 *
457 */
458 public void setByDay(DayAndPosition[] b) {
459 if (b == null) {
460 byDay = null;
461
462 return;
463 }
464
465 byDay = new DayAndPosition[b.length];
466
467 /*
468 * System.arraycopy isn't good enough -- we want to clone each
469 * individual element.
470 */
471 for (int i = 0; i < b.length; i++) {
472 byDay[i] = (DayAndPosition)b[i].clone();
473 }
474 }
475
476 /**
477 * Method getByMonthDay
478 *
479 *
480 * @return int[]
481 *
482 */
483 public int[] getByMonthDay() {
484 if (byMonthDay == null) {
485 return null;
486 }
487
488 int[] b = new int[byMonthDay.length];
489
490 System.arraycopy(byMonthDay, 0, b, 0, byMonthDay.length);
491
492 return b;
493 }
494
495 /**
496 * Method setByMonthDay
497 *
498 *
499 * @param b
500 *
501 */
502 public void setByMonthDay(int[] b) {
503 if (b == null) {
504 byMonthDay = null;
505
506 return;
507 }
508
509 byMonthDay = new int[b.length];
510
511 System.arraycopy(b, 0, byMonthDay, 0, b.length);
512 }
513
514 /**
515 * Method getByYearDay
516 *
517 *
518 * @return int[]
519 *
520 */
521 public int[] getByYearDay() {
522 if (byYearDay == null) {
523 return null;
524 }
525
526 int[] b = new int[byYearDay.length];
527
528 System.arraycopy(byYearDay, 0, b, 0, byYearDay.length);
529
530 return b;
531 }
532
533 /**
534 * Method setByYearDay
535 *
536 *
537 * @param b
538 *
539 */
540 public void setByYearDay(int[] b) {
541 if (b == null) {
542 byYearDay = null;
543
544 return;
545 }
546
547 byYearDay = new int[b.length];
548
549 System.arraycopy(b, 0, byYearDay, 0, b.length);
550 }
551
552 /**
553 * Method getByWeekNo
554 *
555 *
556 * @return int[]
557 *
558 */
559 public int[] getByWeekNo() {
560 if (byWeekNo == null) {
561 return null;
562 }
563
564 int[] b = new int[byWeekNo.length];
565
566 System.arraycopy(byWeekNo, 0, b, 0, byWeekNo.length);
567
568 return b;
569 }
570
571 /**
572 * Method setByWeekNo
573 *
574 *
575 * @param b
576 *
577 */
578 public void setByWeekNo(int[] b) {
579 if (b == null) {
580 byWeekNo = null;
581
582 return;
583 }
584
585 byWeekNo = new int[b.length];
586
587 System.arraycopy(b, 0, byWeekNo, 0, b.length);
588 }
589
590 /**
591 * Method getByMonth
592 *
593 *
594 * @return int[]
595 *
596 */
597 public int[] getByMonth() {
598 if (byMonth == null) {
599 return null;
600 }
601
602 int[] b = new int[byMonth.length];
603
604 System.arraycopy(byMonth, 0, b, 0, byMonth.length);
605
606 return b;
607 }
608
609 /**
610 * Method setByMonth
611 *
612 *
613 * @param b
614 *
615 */
616 public void setByMonth(int[] b) {
617 if (b == null) {
618 byMonth = null;
619
620 return;
621 }
622
623 byMonth = new int[b.length];
624
625 System.arraycopy(b, 0, byMonth, 0, b.length);
626 }
627
628 /**
629 * Method isInRecurrence
630 *
631 *
632 * @param current
633 *
634 * @return boolean
635 *
636 */
637 public boolean isInRecurrence(Calendar current) {
638 return isInRecurrence(current, false);
639 }
640
641 /**
642 * Method isInRecurrence
643 *
644 *
645 * @param current
646 * @param debug
647 *
648 * @return boolean
649 *
650 */
651 public boolean isInRecurrence(Calendar current, boolean debug) {
652 Calendar myCurrent = (Calendar)current.clone();
653
654 // Do all calculations in GMT. Keep other parameters consistent.
655
656 myCurrent.clear(Calendar.ZONE_OFFSET);
657 myCurrent.clear(Calendar.DST_OFFSET);
658 myCurrent.setTimeZone(TimeZone.getTimeZone("GMT"));
659 myCurrent.setMinimalDaysInFirstWeek(4);
660 myCurrent.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
661
662 if (myCurrent.getTime().getTime() < dtStart.getTime().getTime()) {
663
664 // The current time is earlier than the start time.
665
666 if (debug) {
667 System.err.println("current < start");
668 }
669
670 return false;
671 }
672
673 if (myCurrent.getTime().getTime()
674 < dtStart.getTime().getTime() + duration.getInterval()) {
675
676 // We are within "duration" of dtStart.
677
678 if (debug) {
679 System.err.println("within duration of start");
680 }
681
682 return true;
683 }
684
685 Calendar candidate = getCandidateStartTime(myCurrent);
686
687 /* Loop over ranges for the duration. */
688
689 while (candidate.getTime().getTime() + duration.getInterval()
690 > myCurrent.getTime().getTime()) {
691 if (candidateIsInRecurrence(candidate, debug)) {
692 return true;
693 }
694
695 /* Roll back to one second previous, and try again. */
696
697 candidate.add(Calendar.SECOND, -1);
698
699 /* Make sure we haven't rolled back to before dtStart. */
700
701 if (candidate.getTime().getTime() < dtStart.getTime().getTime()) {
702 if (debug) {
703 System.err.println("No candidates after dtStart");
704 }
705
706 return false;
707 }
708
709 candidate = getCandidateStartTime(candidate);
710 }
711
712 if (debug) {
713 System.err.println("No matching candidates");
714 }
715
716 return false;
717 }
718
719 /**
720 * Method candidateIsInRecurrence
721 *
722 *
723 * @param candidate
724 * @param debug
725 *
726 * @return boolean
727 *
728 */
729 protected boolean candidateIsInRecurrence(Calendar candidate,
730 boolean debug) {
731 if ((until != null)
732 && (candidate.getTime().getTime() > until.getTime().getTime())) {
733
734 // After "until"
735
736 if (debug) {
737 System.err.println("after until");
738 }
739
740 return false;
741 }
742
743 if (getRecurrenceCount(candidate) % interval != 0) {
744
745 // Not a repetition of the interval
746
747 if (debug) {
748 System.err.println("not an interval rep");
749 }
750
751 return false;
752 }
753 else if ((occurrence > 0) &&
754 (getRecurrenceCount(candidate) >= occurrence)) {
755
756 return false;
757 }
758
759 if (!matchesByDay(candidate) ||!matchesByMonthDay(candidate)
760 ||!matchesByYearDay(candidate) ||!matchesByWeekNo(candidate)
761 ||!matchesByMonth(candidate)) {
762
763 // Doesn't match a by* rule
764
765 if (debug) {
766 System.err.println("doesn't match a by*");
767 }
768
769 return false;
770 }
771
772 if (debug) {
773 System.err.println("All checks succeeded");
774 }
775
776 return true;
777 }
778
779 /**
780 * Method getMinimumInterval
781 *
782 *
783 * @return int
784 *
785 */
786 protected int getMinimumInterval() {
787 if ((frequency == DAILY) || (byDay != null) || (byMonthDay != null)
788 || (byYearDay != null)) {
789 return DAILY;
790 }
791 else if ((frequency == WEEKLY) || (byWeekNo != null)) {
792 return WEEKLY;
793 }
794 else if ((frequency == MONTHLY) || (byMonth != null)) {
795 return MONTHLY;
796 }
797 else if (frequency == YEARLY) {
798 return YEARLY;
799 }
800 else if (frequency == NO_RECURRENCE) {
801 return NO_RECURRENCE;
802 }
803 else {
804
805 // Shouldn't happen
806
807 throw new IllegalStateException(
808 "Internal error: Unknown frequency value");
809 }
810 }
811
812 /**
813 * Method getCandidateStartTime
814 *
815 *
816 * @param current
817 *
818 * @return Calendar
819 *
820 */
821 public Calendar getCandidateStartTime(Calendar current) {
822 if (dtStart.getTime().getTime() > current.getTime().getTime()) {
823 throw new IllegalArgumentException("Current time before DtStart");
824 }
825
826 int minInterval = getMinimumInterval();
827 Calendar candidate = (Calendar)current.clone();
828
829 if (true) {
830
831 // This block is only needed while this function is public...
832
833 candidate.clear(Calendar.ZONE_OFFSET);
834 candidate.clear(Calendar.DST_OFFSET);
835 candidate.setTimeZone(TimeZone.getTimeZone("GMT"));
836 candidate.setMinimalDaysInFirstWeek(4);
837 candidate.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
838 }
839
840 if (frequency == NO_RECURRENCE) {
841 candidate.setTime(dtStart.getTime());
842
843 return candidate;
844 }
845
846 reduce_constant_length_field(Calendar.SECOND, dtStart, candidate);
847 reduce_constant_length_field(Calendar.MINUTE, dtStart, candidate);
848 reduce_constant_length_field(Calendar.HOUR_OF_DAY, dtStart, candidate);
849
850 switch (minInterval) {
851
852 case DAILY :
853
854 /* No more adjustments needed */
855
856 break;
857
858 case WEEKLY :
859 reduce_constant_length_field(Calendar.DAY_OF_WEEK, dtStart,
860 candidate);
861 break;
862
863 case MONTHLY :
864 reduce_day_of_month(dtStart, candidate);
865 break;
866
867 case YEARLY :
868 reduce_day_of_year(dtStart, candidate);
869 break;
870 }
871
872 return candidate;
873 }
874
875 /**
876 * Method reduce_constant_length_field
877 *
878 *
879 * @param field
880 * @param start
881 * @param candidate
882 *
883 */
884 protected static void reduce_constant_length_field(int field,
885 Calendar start,
886 Calendar candidate) {
887 if ((start.getMaximum(field) != start.getLeastMaximum(field))
888 || (start.getMinimum(field) != start.getGreatestMinimum(field))) {
889 throw new IllegalArgumentException("Not a constant length field");
890 }
891
892 int fieldLength = (start.getMaximum(field) - start.getMinimum(field)
893 + 1);
894 int delta = start.get(field) - candidate.get(field);
895
896 if (delta > 0) {
897 delta -= fieldLength;
898 }
899
900 candidate.add(field, delta);
901 }
902
903 /**
904 * Method reduce_day_of_month
905 *
906 *
907 * @param start
908 * @param candidate
909 *
910 */
911 protected static void reduce_day_of_month(Calendar start,
912 Calendar candidate) {
913 Calendar tempCal = (Calendar)candidate.clone();
914
915 tempCal.add(Calendar.MONTH, -1);
916
917 int delta = start.get(Calendar.DATE) - candidate.get(Calendar.DATE);
918
919 if (delta > 0) {
920 delta -= tempCal.getActualMaximum(Calendar.DATE);
921 }
922
923 candidate.add(Calendar.DATE, delta);
924
925 while (start.get(Calendar.DATE) != candidate.get(Calendar.DATE)) {
926 tempCal.add(Calendar.MONTH, -1);
927 candidate.add(Calendar.DATE,
928 -tempCal.getActualMaximum(Calendar.DATE));
929 }
930 }
931
932 /**
933 * Method reduce_day_of_year
934 *
935 *
936 * @param start
937 * @param candidate
938 *
939 */
940 protected static void reduce_day_of_year(Calendar start,
941 Calendar candidate) {
942 if ((start.get(Calendar.MONTH) > candidate.get(Calendar.MONTH))
943 || ((start.get(Calendar.MONTH) == candidate.get(Calendar.MONTH))
944 && (start.get(Calendar.DATE) > candidate.get(Calendar.DATE)))) {
945 candidate.add(Calendar.YEAR, -1);
946 }
947
948 /* Set the candidate date to the start date. */
949
950 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
951 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
952
953 while ((start.get(Calendar.MONTH) != candidate.get(Calendar.MONTH))
954 || (start.get(Calendar.DATE) != candidate.get(Calendar.DATE))) {
955 candidate.add(Calendar.YEAR, -1);
956 candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
957 candidate.set(Calendar.DATE, start.get(Calendar.DATE));
958 }
959 }
960
961 /**
962 * Method getRecurrenceCount
963 *
964 *
965 * @param candidate
966 *
967 * @return int
968 *
969 */
970 protected int getRecurrenceCount(Calendar candidate) {
971 switch (frequency) {
972
973 case NO_RECURRENCE :
974 return 0;
975
976 case DAILY :
977 return (int)(getDayNumber(candidate) - getDayNumber(dtStart));
978
979 case WEEKLY :
980 Calendar tempCand = (Calendar)candidate.clone();
981
982 tempCand.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
983
984 return (int)(getWeekNumber(tempCand) - getWeekNumber(dtStart));
985
986 case MONTHLY :
987 return (int)(getMonthNumber(candidate)
988 - getMonthNumber(dtStart));
989
990 case YEARLY :
991 return candidate.get(Calendar.YEAR)
992 - dtStart.get(Calendar.YEAR);
993
994 default :
995 throw new IllegalStateException("bad frequency internally...");
996 }
997 }
998
999 /**
1000 * Method getDayNumber
1001 *
1002 *
1003 * @param cal
1004 *
1005 * @return long
1006 *
1007 */
1008 protected static long getDayNumber(Calendar cal) {
1009 Calendar tempCal = (Calendar)cal.clone();
1010
1011 // Set to midnight, GMT
1012
1013 tempCal.set(Calendar.MILLISECOND, 0);
1014 tempCal.set(Calendar.SECOND, 0);
1015 tempCal.set(Calendar.MINUTE, 0);
1016 tempCal.set(Calendar.HOUR_OF_DAY, 0);
1017
1018 return tempCal.getTime().getTime() / (24 * 60 * 60 * 1000);
1019 }
1020
1021 /**
1022 * Method getWeekNumber
1023 *
1024 *
1025 * @param cal
1026 *
1027 * @return long
1028 *
1029 */
1030 protected static long getWeekNumber(Calendar cal) {
1031 Calendar tempCal = (Calendar)cal.clone();
1032
1033 // Set to midnight, GMT
1034
1035 tempCal.set(Calendar.MILLISECOND, 0);
1036 tempCal.set(Calendar.SECOND, 0);
1037 tempCal.set(Calendar.MINUTE, 0);
1038 tempCal.set(Calendar.HOUR_OF_DAY, 0);
1039
1040 // Roll back to the first day of the week
1041
1042 int delta = tempCal.getFirstDayOfWeek()
1043 - tempCal.get(Calendar.DAY_OF_WEEK);
1044
1045 if (delta > 0) {
1046 delta -= 7;
1047 }
1048
1049 // tempCal now points to the first instant of this week.
1050
1051 // Calculate the "week epoch" -- the weekstart day closest to January 1,
1052 // 1970 (which was a Thursday)
1053
1054 long weekEpoch = (tempCal.getFirstDayOfWeek() - Calendar.THURSDAY) * 24
1055 * 60 * 60 * 1000L;
1056
1057 return (tempCal.getTime().getTime() - weekEpoch)
1058 / (7 * 24 * 60 * 60 * 1000);
1059 }
1060
1061 /**
1062 * Method getMonthNumber
1063 *
1064 *
1065 * @param cal
1066 *
1067 * @return long
1068 *
1069 */
1070 protected static long getMonthNumber(Calendar cal) {
1071 return (cal.get(Calendar.YEAR) - 1970) * 12
1072 + (cal.get(Calendar.MONTH) - Calendar.JANUARY);
1073 }
1074
1075 /**
1076 * Method matchesByDay
1077 *
1078 *
1079 * @param candidate
1080 *
1081 * @return boolean
1082 *
1083 */
1084 protected boolean matchesByDay(Calendar candidate) {
1085 if ((byDay == null) || (byDay.length == 0)) {
1086
1087 /* No byDay rules, so it matches trivially */
1088
1089 return true;
1090 }
1091
1092 int i;
1093
1094 for (i = 0; i < byDay.length; i++) {
1095 if (matchesIndividualByDay(candidate, byDay[i])) {
1096 return true;
1097 }
1098 }
1099
1100 return false;
1101 }
1102
1103 /**
1104 * Method matchesIndividualByDay
1105 *
1106 *
1107 * @param candidate
1108 * @param pos
1109 *
1110 * @return boolean
1111 *
1112 */
1113 protected boolean matchesIndividualByDay(Calendar candidate,
1114 DayAndPosition pos) {
1115 if (pos.getDayOfWeek() != candidate.get(Calendar.DAY_OF_WEEK)) {
1116 return false;
1117 }
1118
1119 int position = pos.getDayPosition();
1120
1121 if (position == 0) {
1122 return true;
1123 }
1124
1125 int field;
1126
1127 switch (frequency) {
1128
1129 case MONTHLY :
1130 field = Calendar.DAY_OF_MONTH;
1131 break;
1132
1133 case YEARLY :
1134 field = Calendar.DAY_OF_YEAR;
1135 break;
1136
1137 default :
1138 throw new IllegalStateException(
1139 "byday has a day position "
1140 + "in non-MONTHLY or YEARLY recurrence");
1141 }
1142
1143 if (position > 0) {
1144 int day_of_week_in_field = ((candidate.get(field) - 1) / 7) + 1;
1145
1146 return (position == day_of_week_in_field);
1147 }
1148 else {
1149
1150 /* position < 0 */
1151
1152 int negative_day_of_week_in_field =
1153 ((candidate.getActualMaximum(field) - candidate.get(field)) / 7)
1154 + 1;
1155
1156 return (-position == negative_day_of_week_in_field);
1157 }
1158 }
1159
1160 /**
1161 * Method matchesByField
1162 *
1163 *
1164 * @param array
1165 * @param field
1166 * @param candidate
1167 * @param allowNegative
1168 *
1169 * @return boolean
1170 *
1171 */
1172 protected static boolean matchesByField(int[] array, int field,
1173 Calendar candidate,
1174 boolean allowNegative) {
1175 if ((array == null) || (array.length == 0)) {
1176
1177 /* No rules, so it matches trivially */
1178
1179 return true;
1180 }
1181
1182 int i;
1183
1184 for (i = 0; i < array.length; i++) {
1185 int val;
1186
1187 if (allowNegative && (array[i] < 0)) {
1188
1189 // byMonthDay = -1, in a 31-day month, means 31
1190
1191 int max = candidate.getActualMaximum(field);
1192
1193 val = (max + 1) + array[i];
1194 }
1195 else {
1196 val = array[i];
1197 }
1198
1199 if (val == candidate.get(field)) {
1200 return true;
1201 }
1202 }
1203
1204 return false;
1205 }
1206
1207 /**
1208 * Method matchesByMonthDay
1209 *
1210 *
1211 * @param candidate
1212 *
1213 * @return boolean
1214 *
1215 */
1216 protected boolean matchesByMonthDay(Calendar candidate) {
1217 return matchesByField(byMonthDay, Calendar.DATE, candidate, true);
1218 }
1219
1220 /**
1221 * Method matchesByYearDay
1222 *
1223 *
1224 * @param candidate
1225 *
1226 * @return boolean
1227 *
1228 */
1229 protected boolean matchesByYearDay(Calendar candidate) {
1230 return matchesByField(byYearDay, Calendar.DAY_OF_YEAR, candidate, true);
1231 }
1232
1233 /**
1234 * Method matchesByWeekNo
1235 *
1236 *
1237 * @param candidate
1238 *
1239 * @return boolean
1240 *
1241 */
1242 protected boolean matchesByWeekNo(Calendar candidate) {
1243 return matchesByField(byWeekNo, Calendar.WEEK_OF_YEAR, candidate, true);
1244 }
1245
1246 /**
1247 * Method matchesByMonth
1248 *
1249 *
1250 * @param candidate
1251 *
1252 * @return boolean
1253 *
1254 */
1255 protected boolean matchesByMonth(Calendar candidate) {
1256 return matchesByField(byMonth, Calendar.MONTH, candidate, false);
1257 }
1258
1259 /**
1260 * Method toString
1261 *
1262 *
1263 * @return String
1264 *
1265 */
1266 public String toString() {
1267 StringBuilder sb = new StringBuilder();
1268
1269 sb.append(getClass().getName());
1270 sb.append("[dtStart=");
1271 sb.append((dtStart != null) ? dtStart.toString() : "null");
1272 sb.append(",duration=");
1273 sb.append((duration != null) ? duration.toString() : "null");
1274 sb.append(",frequency=");
1275 sb.append(frequency);
1276 sb.append(",interval=");
1277 sb.append(interval);
1278 sb.append(",until=");
1279 sb.append((until != null) ? until.toString() : "null");
1280 sb.append(",byDay=");
1281
1282 if (byDay == null) {
1283 sb.append("null");
1284 }
1285 else {
1286 sb.append("[");
1287
1288 for (int i = 0; i < byDay.length; i++) {
1289 if (i != 0) {
1290 sb.append(",");
1291 }
1292
1293 if (byDay[i] != null) {
1294 sb.append(byDay[i].toString());
1295 }
1296 else {
1297 sb.append("null");
1298 }
1299 }
1300
1301 sb.append("]");
1302 }
1303
1304 sb.append(",byMonthDay=");
1305 sb.append(stringizeIntArray(byMonthDay));
1306 sb.append(",byYearDay=");
1307 sb.append(stringizeIntArray(byYearDay));
1308 sb.append(",byWeekNo=");
1309 sb.append(stringizeIntArray(byWeekNo));
1310 sb.append(",byMonth=");
1311 sb.append(stringizeIntArray(byMonth));
1312 sb.append(']');
1313
1314 return sb.toString();
1315 }
1316
1317 /**
1318 * Method stringizeIntArray
1319 *
1320 *
1321 * @param a
1322 *
1323 * @return String
1324 *
1325 */
1326 private String stringizeIntArray(int[] a) {
1327 if (a == null) {
1328 return "null";
1329 }
1330
1331 StringBuilder sb = new StringBuilder();
1332
1333 sb.append("[");
1334
1335 for (int i = 0; i < a.length; i++) {
1336 if (i != 0) {
1337 sb.append(",");
1338 }
1339
1340 sb.append(a[i]);
1341 }
1342
1343 sb.append("]");
1344
1345 return sb.toString();
1346 }
1347
1348}