001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    /*
016     * Copyright (c) 2000, Columbia University.  All rights reserved.
017     *
018     * Redistribution and use in source and binary forms, with or without
019     * modification, are permitted provided that the following conditions are met:
020     *
021     * 1. Redistributions of source code must retain the above copyright
022     *        notice, this list of conditions and the following disclaimer.
023     *
024     * 2. Redistributions in binary form must reproduce the above copyright
025     *        notice, this list of conditions and the following disclaimer in the
026     *        documentation and/or other materials provided with the distribution.
027     *
028     * 3. Neither the name of the University nor the names of its contributors
029     *        may be used to endorse or promote products derived from this software
030     *        without specific prior written permission.
031     *
032     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
033     * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
034     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
035     * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
036     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
037     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
038     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
039     * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
040     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
041     * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
042     * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
043     */
044    
045    package com.liferay.util.cal;
046    
047    import com.liferay.portal.kernel.util.CalendarFactoryUtil;
048    import com.liferay.portal.kernel.util.StringBundler;
049    import com.liferay.portal.kernel.util.StringPool;
050    import com.liferay.portal.kernel.util.TimeZoneUtil;
051    
052    import java.io.Serializable;
053    
054    import java.util.Calendar;
055    import java.util.Date;
056    
057    /**
058     * @author     Jonathan Lennox
059     * @deprecated As of 6.2.0, moved to {@link
060     *             com.liferay.portal.kernel.cal.Recurrence}
061     */
062    public class Recurrence implements Serializable {
063    
064            /**
065             * Field DAILY
066             */
067            public static final int DAILY = 3;
068    
069            /**
070             * Field MONTHLY
071             */
072            public static final int MONTHLY = 5;
073    
074            /**
075             * Field NO_RECURRENCE
076             */
077            public static final int NO_RECURRENCE = 7;
078    
079            /**
080             * Field WEEKLY
081             */
082            public static final int WEEKLY = 4;
083    
084            /**
085             * Field YEARLY
086             */
087            public static final int YEARLY = 6;
088    
089            /**
090             * Constructor Recurrence
091             */
092            public Recurrence() {
093                    this(null, new Duration(), NO_RECURRENCE);
094            }
095    
096            /**
097             * Constructor Recurrence
098             */
099            public Recurrence(Calendar start, Duration dur) {
100                    this(start, dur, NO_RECURRENCE);
101            }
102    
103            /**
104             * Constructor Recurrence
105             */
106            public Recurrence(Calendar start, Duration dur, int freq) {
107                    setDtStart(start);
108    
109                    duration = (Duration)dur.clone();
110                    frequency = freq;
111                    interval = 1;
112            }
113    
114            /* Accessors */
115    
116            /**
117             * Method getByDay
118             *
119             * @return DayAndPosition[]
120             */
121            public DayAndPosition[] getByDay() {
122                    if (byDay == null) {
123                            return null;
124                    }
125    
126                    DayAndPosition[] b = new DayAndPosition[byDay.length];
127    
128                    /*
129                     * System.arraycopy isn't good enough -- we want to clone each
130                     * individual element.
131                     */
132                    for (int i = 0; i < byDay.length; i++) {
133                            b[i] = (DayAndPosition)byDay[i].clone();
134                    }
135    
136                    return b;
137            }
138    
139            /**
140             * Method getByMonth
141             *
142             * @return int[]
143             */
144            public int[] getByMonth() {
145                    if (byMonth == null) {
146                            return null;
147                    }
148    
149                    int[] b = new int[byMonth.length];
150    
151                    System.arraycopy(byMonth, 0, b, 0, byMonth.length);
152    
153                    return b;
154            }
155    
156            /**
157             * Method getByMonthDay
158             *
159             * @return int[]
160             */
161            public int[] getByMonthDay() {
162                    if (byMonthDay == null) {
163                            return null;
164                    }
165    
166                    int[] b = new int[byMonthDay.length];
167    
168                    System.arraycopy(byMonthDay, 0, b, 0, byMonthDay.length);
169    
170                    return b;
171            }
172    
173            /**
174             * Method getByWeekNo
175             *
176             * @return int[]
177             */
178            public int[] getByWeekNo() {
179                    if (byWeekNo == null) {
180                            return null;
181                    }
182    
183                    int[] b = new int[byWeekNo.length];
184    
185                    System.arraycopy(byWeekNo, 0, b, 0, byWeekNo.length);
186    
187                    return b;
188            }
189    
190            /**
191             * Method getByYearDay
192             *
193             * @return int[]
194             */
195            public int[] getByYearDay() {
196                    if (byYearDay == null) {
197                            return null;
198                    }
199    
200                    int[] b = new int[byYearDay.length];
201    
202                    System.arraycopy(byYearDay, 0, b, 0, byYearDay.length);
203    
204                    return b;
205            }
206    
207            /**
208             * Method getCandidateStartTime
209             *
210             * @param  current the current time
211             * @return Calendar
212             */
213            public Calendar getCandidateStartTime(Calendar current) {
214                    if (dtStart.getTime().getTime() > current.getTime().getTime()) {
215                            throw new IllegalArgumentException("Current time before DtStart");
216                    }
217    
218                    int minInterval = getMinimumInterval();
219                    Calendar candidate = (Calendar)current.clone();
220    
221                    if (true) {
222    
223                            // This block is only needed while this function is public...
224    
225                            candidate.clear(Calendar.ZONE_OFFSET);
226                            candidate.clear(Calendar.DST_OFFSET);
227                            candidate.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
228                            candidate.setMinimalDaysInFirstWeek(4);
229                            candidate.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
230                    }
231    
232                    if (frequency == NO_RECURRENCE) {
233                            candidate.setTime(dtStart.getTime());
234    
235                            return candidate;
236                    }
237    
238                    reduce_constant_length_field(Calendar.SECOND, dtStart, candidate);
239                    reduce_constant_length_field(Calendar.MINUTE, dtStart, candidate);
240                    reduce_constant_length_field(Calendar.HOUR_OF_DAY, dtStart, candidate);
241    
242                    switch (minInterval) {
243    
244                            case DAILY :
245    
246                                    /* No more adjustments needed */
247    
248                                    break;
249    
250                            case WEEKLY :
251                                    reduce_constant_length_field(
252                                            Calendar.DAY_OF_WEEK, dtStart, candidate);
253                                    break;
254    
255                            case MONTHLY :
256                                    reduce_day_of_month(dtStart, candidate);
257                                    break;
258    
259                            case YEARLY :
260                                    reduce_day_of_year(dtStart, candidate);
261                                    break;
262                    }
263    
264                    return candidate;
265            }
266    
267            /**
268             * Method getDtEnd
269             *
270             * @return Calendar
271             */
272            public Calendar getDtEnd() {
273    
274                    /*
275                     * Make dtEnd a cloned dtStart, so non-time fields of the Calendar
276                     * are accurate.
277                     */
278                    Calendar tempEnd = (Calendar)dtStart.clone();
279    
280                    tempEnd.setTime(
281                            new Date(dtStart.getTime().getTime() + duration.getInterval()));
282    
283                    return tempEnd;
284            }
285    
286            /**
287             * Method getDtStart
288             *
289             * @return Calendar
290             */
291            public Calendar getDtStart() {
292                    return (Calendar)dtStart.clone();
293            }
294    
295            /**
296             * Method getDuration
297             *
298             * @return Duration
299             */
300            public Duration getDuration() {
301                    return (Duration)duration.clone();
302            }
303    
304            /**
305             * Method getFrequency
306             *
307             * @return int
308             */
309            public int getFrequency() {
310                    return frequency;
311            }
312    
313            /**
314             * Method getInterval
315             *
316             * @return int
317             */
318            public int getInterval() {
319                    return interval;
320            }
321    
322            /**
323             * Method getOccurrence
324             *
325             * @return int
326             */
327            public int getOccurrence() {
328                    return occurrence;
329            }
330    
331            /**
332             * Method getUntil
333             *
334             * @return Calendar
335             */
336            public Calendar getUntil() {
337                    return ((until != null) ? (Calendar)until.clone() : null);
338            }
339    
340            /**
341             * Method getWeekStart
342             *
343             * @return int
344             */
345            public int getWeekStart() {
346                    return dtStart.getFirstDayOfWeek();
347            }
348    
349            /**
350             * Method isInRecurrence
351             *
352             * @param  current the current time
353             * @return boolean
354             */
355            public boolean isInRecurrence(Calendar current) {
356                    return isInRecurrence(current, false);
357            }
358    
359            /**
360             * Method isInRecurrence
361             *
362             * @param  current the current time
363             * @param  debug whether to print debug messages
364             * @return boolean
365             */
366            public boolean isInRecurrence(Calendar current, boolean debug) {
367                    Calendar myCurrent = (Calendar)current.clone();
368    
369                    // Do all calculations in GMT.  Keep other parameters consistent.
370    
371                    myCurrent.clear(Calendar.ZONE_OFFSET);
372                    myCurrent.clear(Calendar.DST_OFFSET);
373                    myCurrent.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
374                    myCurrent.setMinimalDaysInFirstWeek(4);
375                    myCurrent.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
376                    myCurrent.set(Calendar.SECOND, 0);
377                    myCurrent.set(Calendar.MILLISECOND, 0);
378    
379                    if (myCurrent.getTime().getTime() < dtStart.getTime().getTime()) {
380    
381                            // The current time is earlier than the start time.
382    
383                            if (debug) {
384                                    System.err.println("current < start");
385                            }
386    
387                            return false;
388                    }
389    
390                    Calendar candidate = getCandidateStartTime(myCurrent);
391    
392                    /* Loop over ranges for the duration. */
393    
394                    while ((candidate.getTime().getTime() + duration.getInterval()) >
395                                            myCurrent.getTime().getTime()) {
396    
397                            if (candidateIsInRecurrence(candidate, debug)) {
398                                    return true;
399                            }
400    
401                            /* Roll back to one second previous, and try again. */
402    
403                            candidate.add(Calendar.SECOND, -1);
404    
405                            /* Make sure we haven't rolled back to before dtStart. */
406    
407                            if (candidate.getTime().getTime() < dtStart.getTime().getTime()) {
408                                    if (debug) {
409                                            System.err.println("No candidates after dtStart");
410                                    }
411    
412                                    return false;
413                            }
414    
415                            candidate = getCandidateStartTime(candidate);
416                    }
417    
418                    if (debug) {
419                            System.err.println("No matching candidates");
420                    }
421    
422                    return false;
423            }
424    
425            /**
426             * Method setByDay
427             */
428            public void setByDay(DayAndPosition[] b) {
429                    if (b == null) {
430                            byDay = null;
431    
432                            return;
433                    }
434    
435                    byDay = new DayAndPosition[b.length];
436    
437                    /*
438                     * System.arraycopy isn't good enough -- we want to clone each
439                     * individual element.
440                     */
441                    for (int i = 0; i < b.length; i++) {
442                            byDay[i] = (DayAndPosition)b[i].clone();
443                    }
444            }
445    
446            /**
447             * Method setByMonth
448             */
449            public void setByMonth(int[] b) {
450                    if (b == null) {
451                            byMonth = null;
452    
453                            return;
454                    }
455    
456                    byMonth = new int[b.length];
457    
458                    System.arraycopy(b, 0, byMonth, 0, b.length);
459            }
460    
461            /**
462             * Method setByMonthDay
463             */
464            public void setByMonthDay(int[] b) {
465                    if (b == null) {
466                            byMonthDay = null;
467    
468                            return;
469                    }
470    
471                    byMonthDay = new int[b.length];
472    
473                    System.arraycopy(b, 0, byMonthDay, 0, b.length);
474            }
475    
476            /**
477             * Method setByWeekNo
478             */
479            public void setByWeekNo(int[] b) {
480                    if (b == null) {
481                            byWeekNo = null;
482    
483                            return;
484                    }
485    
486                    byWeekNo = new int[b.length];
487    
488                    System.arraycopy(b, 0, byWeekNo, 0, b.length);
489            }
490    
491            /**
492             * Method setByYearDay
493             */
494            public void setByYearDay(int[] b) {
495                    if (b == null) {
496                            byYearDay = null;
497    
498                            return;
499                    }
500    
501                    byYearDay = new int[b.length];
502    
503                    System.arraycopy(b, 0, byYearDay, 0, b.length);
504            }
505    
506            /**
507             * Method setDtEnd
508             */
509            public void setDtEnd(Calendar end) {
510                    Calendar tempEnd = (Calendar)end.clone();
511    
512                    tempEnd.clear(Calendar.ZONE_OFFSET);
513                    tempEnd.clear(Calendar.DST_OFFSET);
514                    tempEnd.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
515                    duration.setInterval(
516                            tempEnd.getTime().getTime() - dtStart.getTime().getTime());
517            }
518    
519            /**
520             * Method setDtStart
521             */
522            public void setDtStart(Calendar start) {
523                    int oldStart;
524    
525                    if (dtStart != null) {
526                            oldStart = dtStart.getFirstDayOfWeek();
527                    }
528                    else {
529                            oldStart = Calendar.MONDAY;
530                    }
531    
532                    if (start == null) {
533                            dtStart = CalendarFactoryUtil.getCalendar(
534                                    TimeZoneUtil.getTimeZone(StringPool.UTC));
535    
536                            dtStart.setTime(new Date(0L));
537                    }
538                    else {
539                            dtStart = (Calendar)start.clone();
540    
541                            dtStart.clear(Calendar.ZONE_OFFSET);
542                            dtStart.clear(Calendar.DST_OFFSET);
543                            dtStart.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
544                    }
545    
546                    dtStart.setMinimalDaysInFirstWeek(4);
547                    dtStart.setFirstDayOfWeek(oldStart);
548                    dtStart.set(Calendar.SECOND, 0);
549                    dtStart.set(Calendar.MILLISECOND, 0);
550            }
551    
552            /**
553             * Method setDuration
554             */
555            public void setDuration(Duration d) {
556                    duration = (Duration)d.clone();
557            }
558    
559            /**
560             * Method setFrequency
561             */
562            public void setFrequency(int freq) {
563                    if ((frequency != DAILY) && (frequency != WEEKLY) &&
564                            (frequency != MONTHLY) && (frequency != YEARLY) &&
565                            (frequency != NO_RECURRENCE)) {
566    
567                            throw new IllegalArgumentException("Invalid frequency");
568                    }
569    
570                    frequency = freq;
571            }
572    
573            /**
574             * Method setInterval
575             */
576            public void setInterval(int intr) {
577                    interval = (intr > 0) ? intr : 1;
578            }
579    
580            /**
581             * Method setOccurrence
582             */
583            public void setOccurrence(int occur) {
584                    occurrence = occur;
585            }
586    
587            /**
588             * Method setUntil
589             */
590            public void setUntil(Calendar u) {
591                    if (u == null) {
592                            until = null;
593    
594                            return;
595                    }
596    
597                    until = (Calendar)u.clone();
598    
599                    until.clear(Calendar.ZONE_OFFSET);
600                    until.clear(Calendar.DST_OFFSET);
601                    until.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
602            }
603    
604            /**
605             * Method setWeekStart
606             */
607            public void setWeekStart(int weekstart) {
608                    dtStart.setFirstDayOfWeek(weekstart);
609            }
610    
611            /**
612             * Method toString
613             *
614             * @return String
615             */
616            @Override
617            public String toString() {
618                    StringBundler sb = new StringBundler();
619    
620                    sb.append(getClass().getName());
621                    sb.append("[dtStart=");
622                    sb.append((dtStart != null) ? dtStart.toString() : "null");
623                    sb.append(",duration=");
624                    sb.append((duration != null) ? duration.toString() : "null");
625                    sb.append(",frequency=");
626                    sb.append(frequency);
627                    sb.append(",interval=");
628                    sb.append(interval);
629                    sb.append(",until=");
630                    sb.append((until != null) ? until.toString() : "null");
631                    sb.append(",byDay=");
632    
633                    if (byDay == null) {
634                            sb.append("null");
635                    }
636                    else {
637                            sb.append("[");
638    
639                            for (int i = 0; i < byDay.length; i++) {
640                                    if (i != 0) {
641                                            sb.append(",");
642                                    }
643    
644                                    if (byDay[i] != null) {
645                                            sb.append(byDay[i].toString());
646                                    }
647                                    else {
648                                            sb.append("null");
649                                    }
650                            }
651    
652                            sb.append("]");
653                    }
654    
655                    sb.append(",byMonthDay=");
656                    sb.append(stringizeIntArray(byMonthDay));
657                    sb.append(",byYearDay=");
658                    sb.append(stringizeIntArray(byYearDay));
659                    sb.append(",byWeekNo=");
660                    sb.append(stringizeIntArray(byWeekNo));
661                    sb.append(",byMonth=");
662                    sb.append(stringizeIntArray(byMonth));
663                    sb.append(']');
664    
665                    return sb.toString();
666            }
667    
668            /**
669             * Method getDayNumber
670             *
671             * @return long
672             */
673            protected static long getDayNumber(Calendar cal) {
674                    Calendar tempCal = (Calendar)cal.clone();
675    
676                    // Set to midnight, GMT
677    
678                    tempCal.set(Calendar.MILLISECOND, 0);
679                    tempCal.set(Calendar.SECOND, 0);
680                    tempCal.set(Calendar.MINUTE, 0);
681                    tempCal.set(Calendar.HOUR_OF_DAY, 0);
682    
683                    return tempCal.getTime().getTime() / (24 * 60 * 60 * 1000);
684            }
685    
686            /**
687             * Method getMonthNumber
688             *
689             * @return long
690             */
691            protected static long getMonthNumber(Calendar cal) {
692                    return
693                            ((cal.get(Calendar.YEAR) - 1970) * 12L) +
694                                    ((cal.get(Calendar.MONTH) - Calendar.JANUARY));
695            }
696    
697            /**
698             * Method getWeekNumber
699             *
700             * @return long
701             */
702            protected static long getWeekNumber(Calendar cal) {
703                    Calendar tempCal = (Calendar)cal.clone();
704    
705                    // Set to midnight, GMT
706    
707                    tempCal.set(Calendar.MILLISECOND, 0);
708                    tempCal.set(Calendar.SECOND, 0);
709                    tempCal.set(Calendar.MINUTE, 0);
710                    tempCal.set(Calendar.HOUR_OF_DAY, 0);
711    
712                    // Roll back to the first day of the week
713    
714                    int delta =
715                            tempCal.getFirstDayOfWeek() - tempCal.get(Calendar.DAY_OF_WEEK);
716    
717                    if (delta > 0) {
718                            delta -= 7;
719                    }
720    
721                    // tempCal now points to the first instant of this week.
722    
723                    // Calculate the "week epoch" -- the weekstart day closest to January 1,
724                    // 1970 (which was a Thursday)
725    
726                    long weekEpoch =
727                            (tempCal.getFirstDayOfWeek() - Calendar.THURSDAY) * 24L * 60 * 60 *
728                                    1000;
729    
730                    return
731                            (tempCal.getTime().getTime() - weekEpoch) /
732                                    (7 * 24 * 60 * 60 * 1000);
733            }
734    
735            /**
736             * Method reduce_constant_length_field
737             */
738            protected static void reduce_constant_length_field(
739                    int field, Calendar start, Calendar candidate) {
740    
741                    if ((start.getMaximum(field) != start.getLeastMaximum(field)) ||
742                            (start.getMinimum(field) != start.getGreatestMinimum(field))) {
743    
744                            throw new IllegalArgumentException("Not a constant length field");
745                    }
746    
747                    int fieldLength =
748                            (start.getMaximum(field) - start.getMinimum(field) + 1);
749                    int delta = start.get(field) - candidate.get(field);
750    
751                    if (delta > 0) {
752                            delta -= fieldLength;
753                    }
754    
755                    candidate.add(field, delta);
756            }
757    
758            /**
759             * Method reduce_day_of_month
760             */
761            protected static void reduce_day_of_month(
762                    Calendar start, Calendar candidate) {
763    
764                    Calendar tempCal = (Calendar)candidate.clone();
765    
766                    tempCal.add(Calendar.MONTH, -1);
767    
768                    int delta = start.get(Calendar.DATE) - candidate.get(Calendar.DATE);
769    
770                    if (delta > 0) {
771                            delta -= tempCal.getActualMaximum(Calendar.DATE);
772                    }
773    
774                    candidate.add(Calendar.DATE, delta);
775    
776                    while (start.get(Calendar.DATE) != candidate.get(Calendar.DATE)) {
777                            tempCal.add(Calendar.MONTH, -1);
778                            candidate.add(
779                                    Calendar.DATE, -tempCal.getActualMaximum(Calendar.DATE));
780                    }
781            }
782    
783            /**
784             * Method reduce_day_of_year
785             */
786            protected static void reduce_day_of_year(
787                    Calendar start, Calendar candidate) {
788    
789                    if ((start.get(Calendar.MONTH) > candidate.get(Calendar.MONTH)) ||
790                            ((start.get(Calendar.MONTH) == candidate.get(Calendar.MONTH)) &&
791                             (start.get(Calendar.DATE) > candidate.get(Calendar.DATE)))) {
792    
793                            candidate.add(Calendar.YEAR, -1);
794                    }
795    
796                    /* Set the candidate date to the start date. */
797    
798                    candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
799                    candidate.set(Calendar.DATE, start.get(Calendar.DATE));
800    
801                    while ((start.get(Calendar.MONTH) != candidate.get(Calendar.MONTH)) ||
802                               (start.get(Calendar.DATE) != candidate.get(Calendar.DATE))) {
803    
804                            candidate.add(Calendar.YEAR, -1);
805                            candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
806                            candidate.set(Calendar.DATE, start.get(Calendar.DATE));
807                    }
808            }
809    
810            /**
811             * Method candidateIsInRecurrence
812             *
813             * @return boolean
814             */
815            protected boolean candidateIsInRecurrence(
816                    Calendar candidate, boolean debug) {
817    
818                    if ((until != null) &&
819                            (candidate.getTime().getTime() > until.getTime().getTime())) {
820    
821                            // After "until"
822    
823                            if (debug) {
824                                    System.err.println("after until");
825                            }
826    
827                            return false;
828                    }
829    
830                    if ((getRecurrenceCount(candidate) % interval) != 0) {
831    
832                            // Not a repetition of the interval
833    
834                            if (debug) {
835                                    System.err.println("not an interval rep");
836                            }
837    
838                            return false;
839                    }
840                    else if ((occurrence > 0) &&
841                                     (getRecurrenceCount(candidate) >= occurrence)) {
842    
843                            return false;
844                    }
845    
846                    if (!matchesByDay(candidate) || !matchesByMonthDay(candidate) ||
847                            !matchesByYearDay(candidate) || !matchesByWeekNo(candidate) ||
848                            !matchesByMonth(candidate)) {
849    
850                            // Doesn't match a by* rule
851    
852                            if (debug) {
853                                    System.err.println("doesn't match a by*");
854                            }
855    
856                            return false;
857                    }
858    
859                    if (debug) {
860                            System.err.println("All checks succeeded");
861                    }
862    
863                    return true;
864            }
865    
866            /**
867             * Method getMinimumInterval
868             *
869             * @return int
870             */
871            protected int getMinimumInterval() {
872                    if ((frequency == DAILY) || (byDay != null) || (byMonthDay != null) ||
873                            (byYearDay != null)) {
874    
875                            return DAILY;
876                    }
877                    else if ((frequency == WEEKLY) || (byWeekNo != null)) {
878                            return WEEKLY;
879                    }
880                    else if ((frequency == MONTHLY) || (byMonth != null)) {
881                            return MONTHLY;
882                    }
883                    else if (frequency == YEARLY) {
884                            return YEARLY;
885                    }
886                    else if (frequency == NO_RECURRENCE) {
887                            return NO_RECURRENCE;
888                    }
889                    else {
890    
891                            // Shouldn't happen
892    
893                            throw new IllegalStateException(
894                                    "Internal error: Unknown frequency value");
895                    }
896            }
897    
898            /**
899             * Method getRecurrenceCount
900             *
901             * @return int
902             */
903            protected int getRecurrenceCount(Calendar candidate) {
904                    switch (frequency) {
905    
906                            case NO_RECURRENCE :
907                                    return 0;
908    
909                            case DAILY :
910                                    return (int)(getDayNumber(candidate) - getDayNumber(dtStart));
911    
912                            case WEEKLY :
913                                    Calendar tempCand = (Calendar)candidate.clone();
914    
915                                    tempCand.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
916    
917                                    return (int)(getWeekNumber(tempCand) - getWeekNumber(dtStart));
918    
919                            case MONTHLY :
920                                    return
921                                            (int)(getMonthNumber(candidate) - getMonthNumber(dtStart));
922    
923                            case YEARLY :
924                                    return
925                                            candidate.get(Calendar.YEAR) - dtStart.get(Calendar.YEAR);
926    
927                            default :
928                                    throw new IllegalStateException("bad frequency internally...");
929                    }
930            }
931    
932            /**
933             * Method matchesByDay
934             *
935             * @return boolean
936             */
937            protected boolean matchesByDay(Calendar candidate) {
938                    if ((byDay == null) || (byDay.length == 0)) {
939    
940                            /* No byDay rules, so it matches trivially */
941    
942                            return true;
943                    }
944    
945                    int i;
946    
947                    for (i = 0; i < byDay.length; i++) {
948                            if (matchesIndividualByDay(candidate, byDay[i])) {
949                                    return true;
950                            }
951                    }
952    
953                    return false;
954            }
955    
956            /**
957             * Method matchesByField
958             *
959             * @return boolean
960             */
961            protected boolean matchesByField(
962                    int[] array, int field, Calendar candidate, boolean allowNegative) {
963    
964                    if ((array == null) || (array.length == 0)) {
965    
966                            /* No rules, so it matches trivially */
967    
968                            return true;
969                    }
970    
971                    int i;
972    
973                    for (i = 0; i < array.length; i++) {
974                            int val;
975    
976                            if (allowNegative && (array[i] < 0)) {
977    
978                                    // byMonthDay = -1, in a 31-day month, means 31
979    
980                                    int max = candidate.getActualMaximum(field);
981    
982                                    val = (max + 1) + array[i];
983                            }
984                            else {
985                                    val = array[i];
986                            }
987    
988                            if (val == candidate.get(field)) {
989                                    return true;
990                            }
991                    }
992    
993                    return false;
994            }
995    
996            /**
997             * Method matchesByMonth
998             *
999             * @return boolean
1000             */
1001            protected boolean matchesByMonth(Calendar candidate) {
1002                    return matchesByField(byMonth, Calendar.MONTH, candidate, false);
1003            }
1004    
1005            /**
1006             * Method matchesByMonthDay
1007             *
1008             * @return boolean
1009             */
1010            protected boolean matchesByMonthDay(Calendar candidate) {
1011                    return matchesByField(byMonthDay, Calendar.DATE, candidate, true);
1012            }
1013    
1014            /**
1015             * Method matchesByWeekNo
1016             *
1017             * @return boolean
1018             */
1019            protected boolean matchesByWeekNo(Calendar candidate) {
1020                    return matchesByField(byWeekNo, Calendar.WEEK_OF_YEAR, candidate, true);
1021            }
1022    
1023            /**
1024             * Method matchesByYearDay
1025             *
1026             * @return boolean
1027             */
1028            protected boolean matchesByYearDay(Calendar candidate) {
1029                    return matchesByField(byYearDay, Calendar.DAY_OF_YEAR, candidate, true);
1030            }
1031    
1032            /**
1033             * Method matchesIndividualByDay
1034             *
1035             * @return boolean
1036             */
1037            protected boolean matchesIndividualByDay(
1038                    Calendar candidate, DayAndPosition pos) {
1039    
1040                    if (pos.getDayOfWeek() != candidate.get(Calendar.DAY_OF_WEEK)) {
1041                            return false;
1042                    }
1043    
1044                    int position = pos.getDayPosition();
1045    
1046                    if (position == 0) {
1047                            return true;
1048                    }
1049    
1050                    int field = Calendar.DAY_OF_MONTH;
1051    
1052                    if (position > 0) {
1053                            int candidatePosition = ((candidate.get(field) - 1) / 7) + 1;
1054    
1055                            return (position == candidatePosition);
1056                    }
1057                    else {
1058    
1059                            /* position < 0 */
1060    
1061                            int negativeCandidatePosition =
1062                                    ((candidate.getActualMaximum(field) - candidate.get(field)) /
1063                                            7) + 1;
1064    
1065                            return (-position == negativeCandidatePosition);
1066                    }
1067            }
1068    
1069            /**
1070             * Method stringizeIntArray
1071             *
1072             * @return String
1073             */
1074            protected String stringizeIntArray(int[] a) {
1075                    if (a == null) {
1076                            return "null";
1077                    }
1078    
1079                    StringBundler sb = new StringBundler(2 * a.length + 1);
1080    
1081                    sb.append("[");
1082    
1083                    for (int i = 0; i < a.length; i++) {
1084                            if (i != 0) {
1085                                    sb.append(",");
1086                            }
1087    
1088                            sb.append(a[i]);
1089                    }
1090    
1091                    sb.append("]");
1092    
1093                    return sb.toString();
1094            }
1095    
1096            /**
1097             * Field byDay
1098             */
1099            protected DayAndPosition[] byDay;
1100    
1101            /**
1102             * Field byMonth
1103             */
1104            protected int[] byMonth;
1105    
1106            /**
1107             * Field byMonthDay
1108             */
1109            protected int[] byMonthDay;
1110    
1111            /**
1112             * Field byWeekNo
1113             */
1114            protected int[] byWeekNo;
1115    
1116            /**
1117             * Field byYearDay
1118             */
1119            protected int[] byYearDay;
1120    
1121            /**
1122             * Field dtStart
1123             */
1124            protected Calendar dtStart;
1125    
1126            /**
1127             * Field duration
1128             */
1129            protected Duration duration;
1130    
1131            /**
1132             * Field frequency
1133             */
1134            protected int frequency;
1135    
1136            /**
1137             * Field interval
1138             */
1139            protected int interval;
1140    
1141            /**
1142             * Field interval
1143             */
1144            protected int occurrence = 0;
1145    
1146            /**
1147             * Field until
1148             */
1149            protected Calendar until;
1150    
1151    }