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