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    
377                    if (myCurrent.getTime().getTime() < dtStart.getTime().getTime()) {
378    
379                            // The current time is earlier than the start time.
380    
381                            if (debug) {
382                                    System.err.println("current < start");
383                            }
384    
385                            return false;
386                    }
387    
388                    Calendar candidate = getCandidateStartTime(myCurrent);
389    
390                    /* Loop over ranges for the duration. */
391    
392                    while ((candidate.getTime().getTime() + duration.getInterval()) >
393                                            myCurrent.getTime().getTime()) {
394    
395                            if (candidateIsInRecurrence(candidate, debug)) {
396                                    return true;
397                            }
398    
399                            /* Roll back to one second previous, and try again. */
400    
401                            candidate.add(Calendar.SECOND, -1);
402    
403                            /* Make sure we haven't rolled back to before dtStart. */
404    
405                            if (candidate.getTime().getTime() < dtStart.getTime().getTime()) {
406                                    if (debug) {
407                                            System.err.println("No candidates after dtStart");
408                                    }
409    
410                                    return false;
411                            }
412    
413                            candidate = getCandidateStartTime(candidate);
414                    }
415    
416                    if (debug) {
417                            System.err.println("No matching candidates");
418                    }
419    
420                    return false;
421            }
422    
423            /**
424             * Method setByDay
425             */
426            public void setByDay(DayAndPosition[] b) {
427                    if (b == null) {
428                            byDay = null;
429    
430                            return;
431                    }
432    
433                    byDay = new DayAndPosition[b.length];
434    
435                    /*
436                     * System.arraycopy isn't good enough -- we want to clone each
437                     * individual element.
438                     */
439                    for (int i = 0; i < b.length; i++) {
440                            byDay[i] = (DayAndPosition)b[i].clone();
441                    }
442            }
443    
444            /**
445             * Method setByMonth
446             */
447            public void setByMonth(int[] b) {
448                    if (b == null) {
449                            byMonth = null;
450    
451                            return;
452                    }
453    
454                    byMonth = new int[b.length];
455    
456                    System.arraycopy(b, 0, byMonth, 0, b.length);
457            }
458    
459            /**
460             * Method setByMonthDay
461             */
462            public void setByMonthDay(int[] b) {
463                    if (b == null) {
464                            byMonthDay = null;
465    
466                            return;
467                    }
468    
469                    byMonthDay = new int[b.length];
470    
471                    System.arraycopy(b, 0, byMonthDay, 0, b.length);
472            }
473    
474            /**
475             * Method setByWeekNo
476             */
477            public void setByWeekNo(int[] b) {
478                    if (b == null) {
479                            byWeekNo = null;
480    
481                            return;
482                    }
483    
484                    byWeekNo = new int[b.length];
485    
486                    System.arraycopy(b, 0, byWeekNo, 0, b.length);
487            }
488    
489            /**
490             * Method setByYearDay
491             */
492            public void setByYearDay(int[] b) {
493                    if (b == null) {
494                            byYearDay = null;
495    
496                            return;
497                    }
498    
499                    byYearDay = new int[b.length];
500    
501                    System.arraycopy(b, 0, byYearDay, 0, b.length);
502            }
503    
504            /**
505             * Method setDtEnd
506             */
507            public void setDtEnd(Calendar end) {
508                    Calendar tempEnd = (Calendar)end.clone();
509    
510                    tempEnd.clear(Calendar.ZONE_OFFSET);
511                    tempEnd.clear(Calendar.DST_OFFSET);
512                    tempEnd.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
513                    duration.setInterval(
514                            tempEnd.getTime().getTime() - dtStart.getTime().getTime());
515            }
516    
517            /**
518             * Method setDtStart
519             */
520            public void setDtStart(Calendar start) {
521                    int oldStart;
522    
523                    if (dtStart != null) {
524                            oldStart = dtStart.getFirstDayOfWeek();
525                    }
526                    else {
527                            oldStart = Calendar.MONDAY;
528                    }
529    
530                    if (start == null) {
531                            dtStart = CalendarFactoryUtil.getCalendar(
532                                    TimeZoneUtil.getTimeZone(StringPool.UTC));
533    
534                            dtStart.setTime(new Date(0L));
535                    }
536                    else {
537                            dtStart = (Calendar)start.clone();
538    
539                            dtStart.clear(Calendar.ZONE_OFFSET);
540                            dtStart.clear(Calendar.DST_OFFSET);
541                            dtStart.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
542                    }
543    
544                    dtStart.setMinimalDaysInFirstWeek(4);
545                    dtStart.setFirstDayOfWeek(oldStart);
546            }
547    
548            /**
549             * Method setDuration
550             */
551            public void setDuration(Duration d) {
552                    duration = (Duration)d.clone();
553            }
554    
555            /**
556             * Method setFrequency
557             */
558            public void setFrequency(int freq) {
559                    if ((frequency != DAILY) && (frequency != WEEKLY) &&
560                            (frequency != MONTHLY) && (frequency != YEARLY) &&
561                            (frequency != NO_RECURRENCE)) {
562    
563                            throw new IllegalArgumentException("Invalid frequency");
564                    }
565    
566                    frequency = freq;
567            }
568    
569            /**
570             * Method setInterval
571             */
572            public void setInterval(int intr) {
573                    interval = (intr > 0) ? intr : 1;
574            }
575    
576            /**
577             * Method setOccurrence
578             */
579            public void setOccurrence(int occur) {
580                    occurrence = occur;
581            }
582    
583            /**
584             * Method setUntil
585             */
586            public void setUntil(Calendar u) {
587                    if (u == null) {
588                            until = null;
589    
590                            return;
591                    }
592    
593                    until = (Calendar)u.clone();
594    
595                    until.clear(Calendar.ZONE_OFFSET);
596                    until.clear(Calendar.DST_OFFSET);
597                    until.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
598            }
599    
600            /**
601             * Method setWeekStart
602             */
603            public void setWeekStart(int weekstart) {
604                    dtStart.setFirstDayOfWeek(weekstart);
605            }
606    
607            /**
608             * Method toString
609             *
610             * @return String
611             */
612            @Override
613            public String toString() {
614                    StringBundler sb = new StringBundler();
615    
616                    sb.append(getClass().getName());
617                    sb.append("[dtStart=");
618                    sb.append((dtStart != null) ? dtStart.toString() : "null");
619                    sb.append(",duration=");
620                    sb.append((duration != null) ? duration.toString() : "null");
621                    sb.append(",frequency=");
622                    sb.append(frequency);
623                    sb.append(",interval=");
624                    sb.append(interval);
625                    sb.append(",until=");
626                    sb.append((until != null) ? until.toString() : "null");
627                    sb.append(",byDay=");
628    
629                    if (byDay == null) {
630                            sb.append("null");
631                    }
632                    else {
633                            sb.append("[");
634    
635                            for (int i = 0; i < byDay.length; i++) {
636                                    if (i != 0) {
637                                            sb.append(",");
638                                    }
639    
640                                    if (byDay[i] != null) {
641                                            sb.append(byDay[i].toString());
642                                    }
643                                    else {
644                                            sb.append("null");
645                                    }
646                            }
647    
648                            sb.append("]");
649                    }
650    
651                    sb.append(",byMonthDay=");
652                    sb.append(stringizeIntArray(byMonthDay));
653                    sb.append(",byYearDay=");
654                    sb.append(stringizeIntArray(byYearDay));
655                    sb.append(",byWeekNo=");
656                    sb.append(stringizeIntArray(byWeekNo));
657                    sb.append(",byMonth=");
658                    sb.append(stringizeIntArray(byMonth));
659                    sb.append(']');
660    
661                    return sb.toString();
662            }
663    
664            /**
665             * Method getDayNumber
666             *
667             * @return long
668             */
669            protected static long getDayNumber(Calendar cal) {
670                    Calendar tempCal = (Calendar)cal.clone();
671    
672                    // Set to midnight, GMT
673    
674                    tempCal.set(Calendar.MILLISECOND, 0);
675                    tempCal.set(Calendar.SECOND, 0);
676                    tempCal.set(Calendar.MINUTE, 0);
677                    tempCal.set(Calendar.HOUR_OF_DAY, 0);
678    
679                    return tempCal.getTime().getTime() / (24 * 60 * 60 * 1000);
680            }
681    
682            /**
683             * Method getMonthNumber
684             *
685             * @return long
686             */
687            protected static long getMonthNumber(Calendar cal) {
688                    return
689                            ((cal.get(Calendar.YEAR) - 1970) * 12L) +
690                                    (cal.get(Calendar.MONTH) - Calendar.JANUARY);
691            }
692    
693            /**
694             * Method getWeekNumber
695             *
696             * @return long
697             */
698            protected static long getWeekNumber(Calendar cal) {
699                    Calendar tempCal = (Calendar)cal.clone();
700    
701                    // Set to midnight, GMT
702    
703                    tempCal.set(Calendar.MILLISECOND, 0);
704                    tempCal.set(Calendar.SECOND, 0);
705                    tempCal.set(Calendar.MINUTE, 0);
706                    tempCal.set(Calendar.HOUR_OF_DAY, 0);
707    
708                    // Roll back to the first day of the week
709    
710                    int delta =
711                            tempCal.getFirstDayOfWeek() - tempCal.get(Calendar.DAY_OF_WEEK);
712    
713                    if (delta > 0) {
714                            delta -= 7;
715                    }
716    
717                    // tempCal now points to the first instant of this week.
718    
719                    // Calculate the "week epoch" -- the weekstart day closest to January 1,
720                    // 1970 (which was a Thursday)
721    
722                    long weekEpoch =
723                            (tempCal.getFirstDayOfWeek() - Calendar.THURSDAY) * 24L * 60 * 60 *
724                                    1000L;
725    
726                    return
727                            (tempCal.getTime().getTime() - weekEpoch) /
728                                    (7 * 24 * 60 * 60 * 1000);
729            }
730    
731            /**
732             * Method reduce_constant_length_field
733             */
734            protected static void reduce_constant_length_field(
735                    int field, Calendar start, Calendar candidate) {
736    
737                    if ((start.getMaximum(field) != start.getLeastMaximum(field)) ||
738                            (start.getMinimum(field) != start.getGreatestMinimum(field))) {
739    
740                            throw new IllegalArgumentException("Not a constant length field");
741                    }
742    
743                    int fieldLength =
744                            (start.getMaximum(field) - start.getMinimum(field) + 1);
745                    int delta = start.get(field) - candidate.get(field);
746    
747                    if (delta > 0) {
748                            delta -= fieldLength;
749                    }
750    
751                    candidate.add(field, delta);
752            }
753    
754            /**
755             * Method reduce_day_of_month
756             */
757            protected static void reduce_day_of_month(
758                    Calendar start, Calendar candidate) {
759    
760                    Calendar tempCal = (Calendar)candidate.clone();
761    
762                    tempCal.add(Calendar.MONTH, -1);
763    
764                    int delta = start.get(Calendar.DATE) - candidate.get(Calendar.DATE);
765    
766                    if (delta > 0) {
767                            delta -= tempCal.getActualMaximum(Calendar.DATE);
768                    }
769    
770                    candidate.add(Calendar.DATE, delta);
771    
772                    while (start.get(Calendar.DATE) != candidate.get(Calendar.DATE)) {
773                            tempCal.add(Calendar.MONTH, -1);
774                            candidate.add(
775                                    Calendar.DATE, -tempCal.getActualMaximum(Calendar.DATE));
776                    }
777            }
778    
779            /**
780             * Method reduce_day_of_year
781             */
782            protected static void reduce_day_of_year(
783                    Calendar start, Calendar candidate) {
784    
785                    if ((start.get(Calendar.MONTH) > candidate.get(Calendar.MONTH)) ||
786                            ((start.get(Calendar.MONTH) == candidate.get(Calendar.MONTH)) &&
787                             (start.get(Calendar.DATE) > candidate.get(Calendar.DATE)))) {
788    
789                            candidate.add(Calendar.YEAR, -1);
790                    }
791    
792                    /* Set the candidate date to the start date. */
793    
794                    candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
795                    candidate.set(Calendar.DATE, start.get(Calendar.DATE));
796    
797                    while ((start.get(Calendar.MONTH) != candidate.get(Calendar.MONTH)) ||
798                               (start.get(Calendar.DATE) != candidate.get(Calendar.DATE))) {
799    
800                            candidate.add(Calendar.YEAR, -1);
801                            candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
802                            candidate.set(Calendar.DATE, start.get(Calendar.DATE));
803                    }
804            }
805    
806            /**
807             * Method candidateIsInRecurrence
808             *
809             * @return boolean
810             */
811            protected boolean candidateIsInRecurrence(
812                    Calendar candidate, boolean debug) {
813    
814                    if ((until != null) &&
815                            (candidate.getTime().getTime() > until.getTime().getTime())) {
816    
817                            // After "until"
818    
819                            if (debug) {
820                                    System.err.println("after until");
821                            }
822    
823                            return false;
824                    }
825    
826                    if ((getRecurrenceCount(candidate) % interval) != 0) {
827    
828                            // Not a repetition of the interval
829    
830                            if (debug) {
831                                    System.err.println("not an interval rep");
832                            }
833    
834                            return false;
835                    }
836                    else if ((occurrence > 0) &&
837                                     (getRecurrenceCount(candidate) >= occurrence)) {
838    
839                            return false;
840                    }
841    
842                    if (!matchesByDay(candidate) || !matchesByMonthDay(candidate) ||
843                            !matchesByYearDay(candidate) || !matchesByWeekNo(candidate) ||
844                            !matchesByMonth(candidate)) {
845    
846                            // Doesn't match a by* rule
847    
848                            if (debug) {
849                                    System.err.println("doesn't match a by*");
850                            }
851    
852                            return false;
853                    }
854    
855                    if (debug) {
856                            System.err.println("All checks succeeded");
857                    }
858    
859                    return true;
860            }
861    
862            /**
863             * Method getMinimumInterval
864             *
865             * @return int
866             */
867            protected int getMinimumInterval() {
868                    if ((frequency == DAILY) || (byDay != null) || (byMonthDay != null) ||
869                            (byYearDay != null)) {
870    
871                            return DAILY;
872                    }
873                    else if ((frequency == WEEKLY) || (byWeekNo != null)) {
874                            return WEEKLY;
875                    }
876                    else if ((frequency == MONTHLY) || (byMonth != null)) {
877                            return MONTHLY;
878                    }
879                    else if (frequency == YEARLY) {
880                            return YEARLY;
881                    }
882                    else if (frequency == NO_RECURRENCE) {
883                            return NO_RECURRENCE;
884                    }
885                    else {
886    
887                            // Shouldn't happen
888    
889                            throw new IllegalStateException(
890                                    "Internal error: Unknown frequency value");
891                    }
892            }
893    
894            /**
895             * Method getRecurrenceCount
896             *
897             * @return int
898             */
899            protected int getRecurrenceCount(Calendar candidate) {
900                    switch (frequency) {
901    
902                            case NO_RECURRENCE :
903                                    return 0;
904    
905                            case DAILY :
906                                    return (int)(getDayNumber(candidate) - getDayNumber(dtStart));
907    
908                            case WEEKLY :
909                                    Calendar tempCand = (Calendar)candidate.clone();
910    
911                                    tempCand.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
912    
913                                    return (int)(getWeekNumber(tempCand) - getWeekNumber(dtStart));
914    
915                            case MONTHLY :
916                                    return
917                                            (int)(getMonthNumber(candidate) - getMonthNumber(dtStart));
918    
919                            case YEARLY :
920                                    return
921                                            candidate.get(Calendar.YEAR) - dtStart.get(Calendar.YEAR);
922    
923                            default :
924                                    throw new IllegalStateException("bad frequency internally...");
925                    }
926            }
927    
928            /**
929             * Method matchesByDay
930             *
931             * @return boolean
932             */
933            protected boolean matchesByDay(Calendar candidate) {
934                    if ((byDay == null) || (byDay.length == 0)) {
935    
936                            /* No byDay rules, so it matches trivially */
937    
938                            return true;
939                    }
940    
941                    int i;
942    
943                    for (i = 0; i < byDay.length; i++) {
944                            if (matchesIndividualByDay(candidate, byDay[i])) {
945                                    return true;
946                            }
947                    }
948    
949                    return false;
950            }
951    
952            /**
953             * Method matchesByField
954             *
955             * @return boolean
956             */
957            protected boolean matchesByField(
958                    int[] array, int field, Calendar candidate, boolean allowNegative) {
959    
960                    if ((array == null) || (array.length == 0)) {
961    
962                            /* No rules, so it matches trivially */
963    
964                            return true;
965                    }
966    
967                    int i;
968    
969                    for (i = 0; i < array.length; i++) {
970                            int val;
971    
972                            if (allowNegative && (array[i] < 0)) {
973    
974                                    // byMonthDay = -1, in a 31-day month, means 31
975    
976                                    int max = candidate.getActualMaximum(field);
977    
978                                    val = (max + 1) + array[i];
979                            }
980                            else {
981                                    val = array[i];
982                            }
983    
984                            if (val == candidate.get(field)) {
985                                    return true;
986                            }
987                    }
988    
989                    return false;
990            }
991    
992            /**
993             * Method matchesByMonth
994             *
995             * @return boolean
996             */
997            protected boolean matchesByMonth(Calendar candidate) {
998                    return matchesByField(byMonth, Calendar.MONTH, candidate, false);
999            }
1000    
1001            /**
1002             * Method matchesByMonthDay
1003             *
1004             * @return boolean
1005             */
1006            protected boolean matchesByMonthDay(Calendar candidate) {
1007                    return matchesByField(byMonthDay, Calendar.DATE, candidate, true);
1008            }
1009    
1010            /**
1011             * Method matchesByWeekNo
1012             *
1013             * @return boolean
1014             */
1015            protected boolean matchesByWeekNo(Calendar candidate) {
1016                    return matchesByField(byWeekNo, Calendar.WEEK_OF_YEAR, candidate, true);
1017            }
1018    
1019            /**
1020             * Method matchesByYearDay
1021             *
1022             * @return boolean
1023             */
1024            protected boolean matchesByYearDay(Calendar candidate) {
1025                    return matchesByField(byYearDay, Calendar.DAY_OF_YEAR, candidate, true);
1026            }
1027    
1028            /**
1029             * Method matchesIndividualByDay
1030             *
1031             * @return boolean
1032             */
1033            protected boolean matchesIndividualByDay(
1034                    Calendar candidate, DayAndPosition pos) {
1035    
1036                    if (pos.getDayOfWeek() != candidate.get(Calendar.DAY_OF_WEEK)) {
1037                            return false;
1038                    }
1039    
1040                    int position = pos.getDayPosition();
1041    
1042                    if (position == 0) {
1043                            return true;
1044                    }
1045    
1046                    int field = Calendar.DAY_OF_MONTH;
1047    
1048                    if (position > 0) {
1049                            int candidatePosition = ((candidate.get(field) - 1) / 7) + 1;
1050    
1051                            return (position == candidatePosition);
1052                    }
1053                    else {
1054    
1055                            /* position < 0 */
1056    
1057                            int negativeCandidatePosition =
1058                                    ((candidate.getActualMaximum(field) - candidate.get(field)) /
1059                                            7) + 1;
1060    
1061                            return (-position == negativeCandidatePosition);
1062                    }
1063            }
1064    
1065            /**
1066             * Method stringizeIntArray
1067             *
1068             * @return String
1069             */
1070            protected String stringizeIntArray(int[] a) {
1071                    if (a == null) {
1072                            return "null";
1073                    }
1074    
1075                    StringBundler sb = new StringBundler(2 * a.length + 1);
1076    
1077                    sb.append("[");
1078    
1079                    for (int i = 0; i < a.length; i++) {
1080                            if (i != 0) {
1081                                    sb.append(",");
1082                            }
1083    
1084                            sb.append(a[i]);
1085                    }
1086    
1087                    sb.append("]");
1088    
1089                    return sb.toString();
1090            }
1091    
1092            /**
1093             * Field byDay
1094             */
1095            protected DayAndPosition[] byDay;
1096    
1097            /**
1098             * Field byMonth
1099             */
1100            protected int[] byMonth;
1101    
1102            /**
1103             * Field byMonthDay
1104             */
1105            protected int[] byMonthDay;
1106    
1107            /**
1108             * Field byWeekNo
1109             */
1110            protected int[] byWeekNo;
1111    
1112            /**
1113             * Field byYearDay
1114             */
1115            protected int[] byYearDay;
1116    
1117            /**
1118             * Field dtStart
1119             */
1120            protected Calendar dtStart;
1121    
1122            /**
1123             * Field duration
1124             */
1125            protected Duration duration;
1126    
1127            /**
1128             * Field frequency
1129             */
1130            protected int frequency;
1131    
1132            /**
1133             * Field interval
1134             */
1135            protected int interval;
1136    
1137            /**
1138             * Field interval
1139             */
1140            protected int occurrence = 0;
1141    
1142            /**
1143             * Field until
1144             */
1145            protected Calendar until;
1146    
1147    }