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