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