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