src/net/fortuna/ical4j/model/Recur.java

changeset 0
fb9019fb1bf7
child 3
73bdfa70b04e
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/net/fortuna/ical4j/model/Recur.java	Tue Feb 10 18:12:00 2015 +0100
     1.3 @@ -0,0 +1,1246 @@
     1.4 +/**
     1.5 + * Copyright (c) 2012, Ben Fortuna
     1.6 + * All rights reserved.
     1.7 + *
     1.8 + * Redistribution and use in source and binary forms, with or without
     1.9 + * modification, are permitted provided that the following conditions
    1.10 + * are met:
    1.11 + *
    1.12 + *  o Redistributions of source code must retain the above copyright
    1.13 + * notice, this list of conditions and the following disclaimer.
    1.14 + *
    1.15 + *  o Redistributions in binary form must reproduce the above copyright
    1.16 + * notice, this list of conditions and the following disclaimer in the
    1.17 + * documentation and/or other materials provided with the distribution.
    1.18 + *
    1.19 + *  o Neither the name of Ben Fortuna nor the names of any other contributors
    1.20 + * may be used to endorse or promote products derived from this software
    1.21 + * without specific prior written permission.
    1.22 + *
    1.23 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    1.24 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    1.25 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    1.26 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    1.27 + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    1.28 + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    1.29 + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    1.30 + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    1.31 + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    1.32 + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    1.33 + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    1.34 + */
    1.35 +package net.fortuna.ical4j.model;
    1.36 +
    1.37 +import java.io.IOException;
    1.38 +import java.io.Serializable;
    1.39 +import java.text.ParseException;
    1.40 +import java.util.Calendar;
    1.41 +import java.util.Collections;
    1.42 +import java.util.HashMap;
    1.43 +import java.util.Iterator;
    1.44 +import java.util.List;
    1.45 +import java.util.Map;
    1.46 +import java.util.NoSuchElementException;
    1.47 +import java.util.StringTokenizer;
    1.48 +
    1.49 +import net.fortuna.ical4j.model.parameter.Value;
    1.50 +import net.fortuna.ical4j.util.Configurator;
    1.51 +import net.fortuna.ical4j.util.Dates;
    1.52 +
    1.53 +import org.apache.commons.logging.Log;
    1.54 +import org.apache.commons.logging.LogFactory;
    1.55 +
    1.56 +/**
    1.57 + * $Id$ [18-Apr-2004]
    1.58 + *
    1.59 + * Defines a recurrence.
    1.60 + * @version 2.0
    1.61 + * @author Ben Fortuna
    1.62 + */
    1.63 +public class Recur implements Serializable {
    1.64 +
    1.65 +    private static final long serialVersionUID = -7333226591784095142L;
    1.66 +
    1.67 +    private static final String FREQ = "FREQ";
    1.68 +
    1.69 +    private static final String UNTIL = "UNTIL";
    1.70 +
    1.71 +    private static final String COUNT = "COUNT";
    1.72 +
    1.73 +    private static final String INTERVAL = "INTERVAL";
    1.74 +
    1.75 +    private static final String BYSECOND = "BYSECOND";
    1.76 +
    1.77 +    private static final String BYMINUTE = "BYMINUTE";
    1.78 +
    1.79 +    private static final String BYHOUR = "BYHOUR";
    1.80 +
    1.81 +    private static final String BYDAY = "BYDAY";
    1.82 +
    1.83 +    private static final String BYMONTHDAY = "BYMONTHDAY";
    1.84 +
    1.85 +    private static final String BYYEARDAY = "BYYEARDAY";
    1.86 +
    1.87 +    private static final String BYWEEKNO = "BYWEEKNO";
    1.88 +
    1.89 +    private static final String BYMONTH = "BYMONTH";
    1.90 +
    1.91 +    private static final String BYSETPOS = "BYSETPOS";
    1.92 +
    1.93 +    private static final String WKST = "WKST";
    1.94 +
    1.95 +    /**
    1.96 +     * Second frequency resolution.
    1.97 +     */
    1.98 +    public static final String SECONDLY = "SECONDLY";
    1.99 +
   1.100 +    /**
   1.101 +     * Minute frequency resolution.
   1.102 +     */
   1.103 +    public static final String MINUTELY = "MINUTELY";
   1.104 +
   1.105 +    /**
   1.106 +     * Hour frequency resolution.
   1.107 +     */
   1.108 +    public static final String HOURLY = "HOURLY";
   1.109 +
   1.110 +    /**
   1.111 +     * Day frequency resolution.
   1.112 +     */
   1.113 +    public static final String DAILY = "DAILY";
   1.114 +
   1.115 +    /**
   1.116 +     * Week frequency resolution.
   1.117 +     */
   1.118 +    public static final String WEEKLY = "WEEKLY";
   1.119 +
   1.120 +    /**
   1.121 +     * Month frequency resolution.
   1.122 +     */
   1.123 +    public static final String MONTHLY = "MONTHLY";
   1.124 +
   1.125 +    /**
   1.126 +     * Year frequency resolution.
   1.127 +     */
   1.128 +    public static final String YEARLY = "YEARLY";
   1.129 +
   1.130 +    /**
   1.131 +     * When calculating dates matching this recur ({@code getDates()} or {@code getNextDate}),
   1.132 +     *  this property defines the maximum number of attempt to find a matching date by
   1.133 +     * incrementing the seed.
   1.134 +     * <p>The default value is 1000. A value of -1 corresponds to no maximum.</p>
   1.135 +     */
   1.136 +    public static final String KEY_MAX_INCREMENT_COUNT = "net.fortuna.ical4j.recur.maxincrementcount";
   1.137 +
   1.138 +    private static int maxIncrementCount;
   1.139 +    static {
   1.140 +        final String value = Configurator.getProperty(KEY_MAX_INCREMENT_COUNT);
   1.141 +        if (value != null && value.length() > 0) {
   1.142 +            maxIncrementCount = Integer.parseInt(value);
   1.143 +        } else {
   1.144 +            maxIncrementCount = 1000;
   1.145 +        }
   1.146 +    }
   1.147 +
   1.148 +    private transient Log log = LogFactory.getLog(Recur.class);
   1.149 +
   1.150 +    private String frequency;
   1.151 +
   1.152 +    private Date until;
   1.153 +
   1.154 +    private int count = -1;
   1.155 +
   1.156 +    private int interval = -1;
   1.157 +
   1.158 +    private NumberList secondList;
   1.159 +
   1.160 +    private NumberList minuteList;
   1.161 +
   1.162 +    private NumberList hourList;
   1.163 +
   1.164 +    private WeekDayList dayList;
   1.165 +
   1.166 +    private NumberList monthDayList;
   1.167 +
   1.168 +    private NumberList yearDayList;
   1.169 +
   1.170 +    private NumberList weekNoList;
   1.171 +
   1.172 +    private NumberList monthList;
   1.173 +
   1.174 +    private NumberList setPosList;
   1.175 +
   1.176 +    private String weekStartDay;
   1.177 +
   1.178 +    private Map experimentalValues = new HashMap();
   1.179 +
   1.180 +    // Calendar field we increment based on frequency.
   1.181 +    private int calIncField;
   1.182 +
   1.183 +    /**
   1.184 +     * Default constructor.
   1.185 +     */
   1.186 +    public Recur() {
   1.187 +    }
   1.188 +    
   1.189 +    /**
   1.190 +     * Constructs a new instance from the specified string value.
   1.191 +     * @param aValue a string representation of a recurrence.
   1.192 +     * @throws ParseException thrown when the specified string contains an invalid representation of an UNTIL date value
   1.193 +     */
   1.194 +    public Recur(final String aValue) throws ParseException {
   1.195 +        final StringTokenizer t = new StringTokenizer(aValue, ";=");
   1.196 +        while (t.hasMoreTokens()) {
   1.197 +            final String token = t.nextToken();
   1.198 +            if (FREQ.equals(token)) {
   1.199 +                frequency = nextToken(t, token);
   1.200 +            }
   1.201 +            else if (UNTIL.equals(token)) {
   1.202 +                final String untilString = nextToken(t, token);
   1.203 +                if (untilString != null && untilString.indexOf("T") >= 0) {
   1.204 +                    until = new DateTime(untilString);
   1.205 +                    // UNTIL must be specified in UTC time..
   1.206 +                    ((DateTime) until).setUtc(true);
   1.207 +                }
   1.208 +                else {
   1.209 +                    until = new Date(untilString);
   1.210 +                }
   1.211 +            }
   1.212 +            else if (COUNT.equals(token)) {
   1.213 +                count = Integer.parseInt(nextToken(t, token));
   1.214 +            }
   1.215 +            else if (INTERVAL.equals(token)) {
   1.216 +                interval = Integer.parseInt(nextToken(t, token));
   1.217 +            }
   1.218 +            else if (BYSECOND.equals(token)) {
   1.219 +                secondList = new NumberList(nextToken(t, token), 0, 59, false);
   1.220 +            }
   1.221 +            else if (BYMINUTE.equals(token)) {
   1.222 +                minuteList = new NumberList(nextToken(t, token), 0, 59, false);
   1.223 +            }
   1.224 +            else if (BYHOUR.equals(token)) {
   1.225 +                hourList = new NumberList(nextToken(t, token), 0, 23, false);
   1.226 +            }
   1.227 +            else if (BYDAY.equals(token)) {
   1.228 +                dayList = new WeekDayList(nextToken(t, token));
   1.229 +            }
   1.230 +            else if (BYMONTHDAY.equals(token)) {
   1.231 +                monthDayList = new NumberList(nextToken(t, token), 1, 31, true);
   1.232 +            }
   1.233 +            else if (BYYEARDAY.equals(token)) {
   1.234 +                yearDayList = new NumberList(nextToken(t, token), 1, 366, true);
   1.235 +            }
   1.236 +            else if (BYWEEKNO.equals(token)) {
   1.237 +                weekNoList = new NumberList(nextToken(t, token), 1, 53, true);
   1.238 +            }
   1.239 +            else if (BYMONTH.equals(token)) {
   1.240 +                monthList = new NumberList(nextToken(t, token), 1, 12, false);
   1.241 +            }
   1.242 +            else if (BYSETPOS.equals(token)) {
   1.243 +                setPosList = new NumberList(nextToken(t, token), 1, 366, true);
   1.244 +            }
   1.245 +            else if (WKST.equals(token)) {
   1.246 +                weekStartDay = nextToken(t, token);
   1.247 +            }
   1.248 +            // assume experimental value..
   1.249 +            else {
   1.250 +                experimentalValues.put(token, nextToken(t, token));
   1.251 +            }
   1.252 +        }
   1.253 +        validateFrequency();
   1.254 +    }
   1.255 +
   1.256 +    private String nextToken(StringTokenizer t, String lastToken) {
   1.257 +        try {
   1.258 +            return t.nextToken();
   1.259 +        }
   1.260 +        catch (NoSuchElementException e) {
   1.261 +            throw new IllegalArgumentException("Missing expected token, last token: " + lastToken);
   1.262 +        }
   1.263 +    }
   1.264 +    
   1.265 +    /**
   1.266 +     * @param frequency a recurrence frequency string
   1.267 +     * @param until maximum recurrence date
   1.268 +     */
   1.269 +    public Recur(final String frequency, final Date until) {
   1.270 +        this.frequency = frequency;
   1.271 +        this.until = until;
   1.272 +        validateFrequency();
   1.273 +    }
   1.274 +
   1.275 +    /**
   1.276 +     * @param frequency a recurrence frequency string
   1.277 +     * @param count maximum recurrence count
   1.278 +     */
   1.279 +    public Recur(final String frequency, final int count) {
   1.280 +        this.frequency = frequency;
   1.281 +        this.count = count;
   1.282 +        validateFrequency();
   1.283 +    }
   1.284 +
   1.285 +    /**
   1.286 +     * @return Returns the dayList.
   1.287 +     */
   1.288 +    public final WeekDayList getDayList() {
   1.289 +        if (dayList == null) {
   1.290 +            dayList = new WeekDayList();
   1.291 +        }
   1.292 +        return dayList;
   1.293 +    }
   1.294 +
   1.295 +    /**
   1.296 +     * @return Returns the hourList.
   1.297 +     */
   1.298 +    public final NumberList getHourList() {
   1.299 +        if (hourList == null) {
   1.300 +            hourList = new NumberList(0, 23, false);
   1.301 +        }
   1.302 +        return hourList;
   1.303 +    }
   1.304 +
   1.305 +    /**
   1.306 +     * @return Returns the minuteList.
   1.307 +     */
   1.308 +    public final NumberList getMinuteList() {
   1.309 +        if (minuteList == null) {
   1.310 +            minuteList = new NumberList(0, 59, false);
   1.311 +        }
   1.312 +        return minuteList;
   1.313 +    }
   1.314 +
   1.315 +    /**
   1.316 +     * @return Returns the monthDayList.
   1.317 +     */
   1.318 +    public final NumberList getMonthDayList() {
   1.319 +        if (monthDayList == null) {
   1.320 +            monthDayList = new NumberList(1, 31, true);
   1.321 +        }
   1.322 +        return monthDayList;
   1.323 +    }
   1.324 +
   1.325 +    /**
   1.326 +     * @return Returns the monthList.
   1.327 +     */
   1.328 +    public final NumberList getMonthList() {
   1.329 +        if (monthList == null) {
   1.330 +            monthList = new NumberList(1, 12, false);
   1.331 +        }
   1.332 +        return monthList;
   1.333 +    }
   1.334 +
   1.335 +    /**
   1.336 +     * @return Returns the secondList.
   1.337 +     */
   1.338 +    public final NumberList getSecondList() {
   1.339 +        if (secondList == null) {
   1.340 +            secondList = new NumberList(0, 59, false);
   1.341 +        }
   1.342 +        return secondList;
   1.343 +    }
   1.344 +
   1.345 +    /**
   1.346 +     * @return Returns the setPosList.
   1.347 +     */
   1.348 +    public final NumberList getSetPosList() {
   1.349 +        if (setPosList == null) {
   1.350 +            setPosList = new NumberList(1, 366, true);
   1.351 +        }
   1.352 +        return setPosList;
   1.353 +    }
   1.354 +
   1.355 +    /**
   1.356 +     * @return Returns the weekNoList.
   1.357 +     */
   1.358 +    public final NumberList getWeekNoList() {
   1.359 +        if (weekNoList == null) {
   1.360 +            weekNoList = new NumberList(1, 53, true);
   1.361 +        }
   1.362 +        return weekNoList;
   1.363 +    }
   1.364 +
   1.365 +    /**
   1.366 +     * @return Returns the yearDayList.
   1.367 +     */
   1.368 +    public final NumberList getYearDayList() {
   1.369 +        if (yearDayList == null) {
   1.370 +            yearDayList = new NumberList(1, 366, true);
   1.371 +        }
   1.372 +        return yearDayList;
   1.373 +    }
   1.374 +
   1.375 +    /**
   1.376 +     * @return Returns the count or -1 if the rule does not have a count.
   1.377 +     */
   1.378 +    public final int getCount() {
   1.379 +        return count;
   1.380 +    }
   1.381 +
   1.382 +    /**
   1.383 +     * @return Returns the experimentalValues.
   1.384 +     */
   1.385 +    public final Map getExperimentalValues() {
   1.386 +        return experimentalValues;
   1.387 +    }
   1.388 +
   1.389 +    /**
   1.390 +     * @return Returns the frequency.
   1.391 +     */
   1.392 +    public final String getFrequency() {
   1.393 +        return frequency;
   1.394 +    }
   1.395 +
   1.396 +    /**
   1.397 +     * @return Returns the interval or -1 if the rule does not have an interval defined.
   1.398 +     */
   1.399 +    public final int getInterval() {
   1.400 +        return interval;
   1.401 +    }
   1.402 +
   1.403 +    /**
   1.404 +     * @return Returns the until or null if there is none.
   1.405 +     */
   1.406 +    public final Date getUntil() {
   1.407 +        return until;
   1.408 +    }
   1.409 +
   1.410 +    /**
   1.411 +     * @return Returns the weekStartDay or null if there is none.
   1.412 +     */
   1.413 +    public final String getWeekStartDay() {
   1.414 +        return weekStartDay;
   1.415 +    }
   1.416 +
   1.417 +    /**
   1.418 +     * @param weekStartDay The weekStartDay to set.
   1.419 +     */
   1.420 +    public final void setWeekStartDay(final String weekStartDay) {
   1.421 +        this.weekStartDay = weekStartDay;
   1.422 +    }
   1.423 +
   1.424 +    /**
   1.425 +     * {@inheritDoc}
   1.426 +     */
   1.427 +    public final String toString() {
   1.428 +        final StringBuffer b = new StringBuffer();
   1.429 +        b.append(FREQ);
   1.430 +        b.append('=');
   1.431 +        b.append(frequency);
   1.432 +        if (weekStartDay != null) {
   1.433 +            b.append(';');
   1.434 +            b.append(WKST);
   1.435 +            b.append('=');
   1.436 +            b.append(weekStartDay);
   1.437 +        }
   1.438 +        if (until != null) {
   1.439 +            b.append(';');
   1.440 +            b.append(UNTIL);
   1.441 +            b.append('=');
   1.442 +            // Note: date-time representations should always be in UTC time.
   1.443 +            b.append(until);
   1.444 +        }
   1.445 +        if (count >= 1) {
   1.446 +            b.append(';');
   1.447 +            b.append(COUNT);
   1.448 +            b.append('=');
   1.449 +            b.append(count);
   1.450 +        }
   1.451 +        if (interval >= 1) {
   1.452 +            b.append(';');
   1.453 +            b.append(INTERVAL);
   1.454 +            b.append('=');
   1.455 +            b.append(interval);
   1.456 +        }
   1.457 +        if (!getMonthList().isEmpty()) {
   1.458 +            b.append(';');
   1.459 +            b.append(BYMONTH);
   1.460 +            b.append('=');
   1.461 +            b.append(monthList);
   1.462 +        }
   1.463 +        if (!getWeekNoList().isEmpty()) {
   1.464 +            b.append(';');
   1.465 +            b.append(BYWEEKNO);
   1.466 +            b.append('=');
   1.467 +            b.append(weekNoList);
   1.468 +        }
   1.469 +        if (!getYearDayList().isEmpty()) {
   1.470 +            b.append(';');
   1.471 +            b.append(BYYEARDAY);
   1.472 +            b.append('=');
   1.473 +            b.append(yearDayList);
   1.474 +        }
   1.475 +        if (!getMonthDayList().isEmpty()) {
   1.476 +            b.append(';');
   1.477 +            b.append(BYMONTHDAY);
   1.478 +            b.append('=');
   1.479 +            b.append(monthDayList);
   1.480 +        }
   1.481 +        if (!getDayList().isEmpty()) {
   1.482 +            b.append(';');
   1.483 +            b.append(BYDAY);
   1.484 +            b.append('=');
   1.485 +            b.append(dayList);
   1.486 +        }
   1.487 +        if (!getHourList().isEmpty()) {
   1.488 +            b.append(';');
   1.489 +            b.append(BYHOUR);
   1.490 +            b.append('=');
   1.491 +            b.append(hourList);
   1.492 +        }
   1.493 +        if (!getMinuteList().isEmpty()) {
   1.494 +            b.append(';');
   1.495 +            b.append(BYMINUTE);
   1.496 +            b.append('=');
   1.497 +            b.append(minuteList);
   1.498 +        }
   1.499 +        if (!getSecondList().isEmpty()) {
   1.500 +            b.append(';');
   1.501 +            b.append(BYSECOND);
   1.502 +            b.append('=');
   1.503 +            b.append(secondList);
   1.504 +        }
   1.505 +        if (!getSetPosList().isEmpty()) {
   1.506 +            b.append(';');
   1.507 +            b.append(BYSETPOS);
   1.508 +            b.append('=');
   1.509 +            b.append(setPosList);
   1.510 +        }
   1.511 +        return b.toString();
   1.512 +    }
   1.513 +
   1.514 +    /**
   1.515 +     * Returns a list of start dates in the specified period represented by this recur. Any date fields not specified by
   1.516 +     * this recur are retained from the period start, and as such you should ensure the period start is initialised
   1.517 +     * correctly.
   1.518 +     * @param periodStart the start of the period
   1.519 +     * @param periodEnd the end of the period
   1.520 +     * @param value the type of dates to generate (i.e. date/date-time)
   1.521 +     * @return a list of dates
   1.522 +     */
   1.523 +    public final DateList getDates(final Date periodStart,
   1.524 +            final Date periodEnd, final Value value) {
   1.525 +        return getDates(periodStart, periodStart, periodEnd, value, -1);
   1.526 +    }
   1.527 +
   1.528 +    /**
   1.529 +     * Convenience method for retrieving recurrences in a specified period.
   1.530 +     * @param seed a seed date for generating recurrence instances
   1.531 +     * @param period the period of returned recurrence dates
   1.532 +     * @param value type of dates to generate
   1.533 +     * @return a list of dates
   1.534 +     */
   1.535 +    public final DateList getDates(final Date seed, final Period period,
   1.536 +            final Value value) {
   1.537 +        return getDates(seed, period.getStart(), period.getEnd(), value, -1);
   1.538 +    }
   1.539 +
   1.540 +    /**
   1.541 +     * Returns a list of start dates in the specified period represented by this recur. This method includes a base date
   1.542 +     * argument, which indicates the start of the fist occurrence of this recurrence. The base date is used to inject
   1.543 +     * default values to return a set of dates in the correct format. For example, if the search start date (start) is
   1.544 +     * Wed, Mar 23, 12:19PM, but the recurrence is Mon - Fri, 9:00AM - 5:00PM, the start dates returned should all be at
   1.545 +     * 9:00AM, and not 12:19PM.
   1.546 +     * @return a list of dates represented by this recur instance
   1.547 +     * @param seed the start date of this Recurrence's first instance
   1.548 +     * @param periodStart the start of the period
   1.549 +     * @param periodEnd the end of the period
   1.550 +     * @param value the type of dates to generate (i.e. date/date-time)
   1.551 +     */
   1.552 +    public final DateList getDates(final Date seed, final Date periodStart,
   1.553 +            final Date periodEnd, final Value value) {
   1.554 +         return getDates(seed, periodStart, periodEnd, value, -1);
   1.555 +    }
   1.556 +
   1.557 +    /**
   1.558 +     * Returns a list of start dates in the specified period represented by this recur. This method includes a base date
   1.559 +     * argument, which indicates the start of the fist occurrence of this recurrence. The base date is used to inject
   1.560 +     * default values to return a set of dates in the correct format. For example, if the search start date (start) is
   1.561 +     * Wed, Mar 23, 12:19PM, but the recurrence is Mon - Fri, 9:00AM - 5:00PM, the start dates returned should all be at
   1.562 +     * 9:00AM, and not 12:19PM.
   1.563 +     * @return a list of dates represented by this recur instance
   1.564 +     * @param seed the start date of this Recurrence's first instance
   1.565 +     * @param periodStart the start of the period
   1.566 +     * @param periodEnd the end of the period
   1.567 +     * @param value the type of dates to generate (i.e. date/date-time)
   1.568 +     * @param maxCount limits the number of instances returned. Up to one years
   1.569 +     *       worth extra may be returned. Less than 0 means no limit
   1.570 +     */
   1.571 +    public final DateList getDates(final Date seed, final Date periodStart,
   1.572 +                                   final Date periodEnd, final Value value,
   1.573 +                                   final int maxCount) {
   1.574 +
   1.575 +        final DateList dates = new DateList(value);
   1.576 +        if (seed instanceof DateTime) {
   1.577 +            if (((DateTime) seed).isUtc()) {
   1.578 +                dates.setUtc(true);
   1.579 +            }
   1.580 +            else {
   1.581 +                dates.setTimeZone(((DateTime) seed).getTimeZone());
   1.582 +            }
   1.583 +        }
   1.584 +        final Calendar cal = Dates.getCalendarInstance(seed);
   1.585 +        cal.setTime(seed);
   1.586 +
   1.587 +        // optimize the start time for selecting candidates
   1.588 +        // (only applicable where a COUNT is not specified)
   1.589 +        if (getCount() < 1) {
   1.590 +            final Calendar seededCal = (Calendar) cal.clone();
   1.591 +            while (seededCal.getTime().before(periodStart)) {
   1.592 +                cal.setTime(seededCal.getTime());
   1.593 +                increment(seededCal);
   1.594 +            }
   1.595 +        }
   1.596 +
   1.597 +        int invalidCandidateCount = 0;
   1.598 +        int noCandidateIncrementCount = 0;
   1.599 +        Date candidate = null;
   1.600 +        while ((maxCount < 0) || (dates.size() < maxCount)) {
   1.601 +            final Date candidateSeed = Dates.getInstance(cal.getTime(), value);
   1.602 +
   1.603 +            if (getUntil() != null && candidate != null
   1.604 +                    && candidate.after(getUntil())) {
   1.605 +
   1.606 +                break;
   1.607 +            }
   1.608 +            if (periodEnd != null && candidate != null
   1.609 +                    && candidate.after(periodEnd)) {
   1.610 +
   1.611 +                break;
   1.612 +            }
   1.613 +            if (getCount() >= 1
   1.614 +                    && (dates.size() + invalidCandidateCount) >= getCount()) {
   1.615 +
   1.616 +                break;
   1.617 +            }
   1.618 +
   1.619 +//            if (Value.DATE_TIME.equals(value)) {
   1.620 +            if (candidateSeed instanceof DateTime) {
   1.621 +                if (dates.isUtc()) {
   1.622 +                    ((DateTime) candidateSeed).setUtc(true);
   1.623 +                }
   1.624 +                else {
   1.625 +                    ((DateTime) candidateSeed).setTimeZone(dates.getTimeZone());
   1.626 +                }
   1.627 +            }
   1.628 +
   1.629 +            final DateList candidates = getCandidates(candidateSeed, value);
   1.630 +            if (!candidates.isEmpty()) {
   1.631 +                noCandidateIncrementCount = 0;
   1.632 +                // sort candidates for identifying when UNTIL date is exceeded..
   1.633 +                Collections.sort(candidates);
   1.634 +                for (final Iterator i = candidates.iterator(); i.hasNext();) {
   1.635 +                    candidate = (Date) i.next();
   1.636 +                    // don't count candidates that occur before the seed date..
   1.637 +                    if (!candidate.before(seed)) {
   1.638 +                        // candidates exclusive of periodEnd..
   1.639 +                        if (candidate.before(periodStart)
   1.640 +                                || !candidate.before(periodEnd)) {
   1.641 +                            invalidCandidateCount++;
   1.642 +                        } else if (getCount() >= 1
   1.643 +                                && (dates.size() + invalidCandidateCount) >= getCount()) {
   1.644 +                            break;
   1.645 +                        } else if (!(getUntil() != null
   1.646 +                                && candidate.after(getUntil()))) {
   1.647 +                            dates.add(candidate);
   1.648 +                        }
   1.649 +                    }
   1.650 +                }
   1.651 +            } else {
   1.652 +                noCandidateIncrementCount++;
   1.653 +                if ((maxIncrementCount > 0) && (noCandidateIncrementCount > maxIncrementCount)) {
   1.654 +                    break;
   1.655 +                }
   1.656 +            }
   1.657 +            increment(cal);
   1.658 +        }
   1.659 +        // sort final list..
   1.660 +        Collections.sort(dates);
   1.661 +        return dates;
   1.662 +    }
   1.663 +    
   1.664 +    /**
   1.665 +     * Returns the the next date of this recurrence given a seed date
   1.666 +     * and start date.  The seed date indicates the start of the fist 
   1.667 +     * occurrence of this recurrence. The start date is the
   1.668 +     * starting date to search for the next recurrence.  Return null
   1.669 +     * if there is no occurrence date after start date.
   1.670 +     * @return the next date in the recurrence series after startDate
   1.671 +     * @param seed the start date of this Recurrence's first instance
   1.672 +     * @param startDate the date to start the search
   1.673 +     */
   1.674 +    public final Date getNextDate(final Date seed, final Date startDate) {
   1.675 +
   1.676 +        final Calendar cal = Dates.getCalendarInstance(seed);
   1.677 +        cal.setTime(seed);
   1.678 +
   1.679 +        // optimize the start time for selecting candidates
   1.680 +        // (only applicable where a COUNT is not specified)
   1.681 +        if (getCount() < 1) {
   1.682 +            final Calendar seededCal = (Calendar) cal.clone();
   1.683 +            while (seededCal.getTime().before(startDate)) {
   1.684 +                cal.setTime(seededCal.getTime());
   1.685 +                increment(seededCal);
   1.686 +            }
   1.687 +        }
   1.688 +
   1.689 +        int invalidCandidateCount = 0;
   1.690 +        int noCandidateIncrementCount = 0;
   1.691 +        Date candidate = null;
   1.692 +        final Value value = seed instanceof DateTime ? Value.DATE_TIME : Value.DATE;
   1.693 +        
   1.694 +        while (true) {
   1.695 +            final Date candidateSeed = Dates.getInstance(cal.getTime(), value);
   1.696 +
   1.697 +            if (getUntil() != null && candidate != null && candidate.after(getUntil())) {
   1.698 +                break;
   1.699 +            }
   1.700 +            
   1.701 +            if (getCount() > 0 && invalidCandidateCount >= getCount()) {
   1.702 +                break;
   1.703 +            }
   1.704 +
   1.705 +            if (Value.DATE_TIME.equals(value)) {
   1.706 +                if (((DateTime) seed).isUtc()) {
   1.707 +                    ((DateTime) candidateSeed).setUtc(true);
   1.708 +                }
   1.709 +                else {
   1.710 +                    ((DateTime) candidateSeed).setTimeZone(((DateTime) seed).getTimeZone());
   1.711 +                }
   1.712 +            }
   1.713 +
   1.714 +            final DateList candidates = getCandidates(candidateSeed, value);
   1.715 +            if (!candidates.isEmpty()) {
   1.716 +                noCandidateIncrementCount = 0;
   1.717 +                // sort candidates for identifying when UNTIL date is exceeded..
   1.718 +                Collections.sort(candidates);
   1.719 +
   1.720 +                for (final Iterator i = candidates.iterator(); i.hasNext();) {
   1.721 +                    candidate = (Date) i.next();
   1.722 +                    // don't count candidates that occur before the seed date..
   1.723 +                    if (!candidate.before(seed)) {
   1.724 +                        // Candidate must be after startDate because
   1.725 +                        // we want the NEXT occurrence
   1.726 +                        if (!candidate.after(startDate)) {
   1.727 +                            invalidCandidateCount++;
   1.728 +                        } else if (getCount() > 0
   1.729 +                                && invalidCandidateCount >= getCount()) {
   1.730 +                            break;
   1.731 +                        } else if (!(getUntil() != null
   1.732 +                                && candidate.after(getUntil()))) {
   1.733 +                            return candidate;
   1.734 +                        }
   1.735 +                    }
   1.736 +                }
   1.737 +            } else {
   1.738 +                noCandidateIncrementCount++;
   1.739 +                if ((maxIncrementCount > 0) && (noCandidateIncrementCount > maxIncrementCount)) {
   1.740 +                    break;
   1.741 +                }
   1.742 +            }
   1.743 +            increment(cal);
   1.744 +        }
   1.745 +        return null;
   1.746 +    }
   1.747 +
   1.748 +    /**
   1.749 +     * Increments the specified calendar according to the frequency and interval specified in this recurrence rule.
   1.750 +     * @param cal a java.util.Calendar to increment
   1.751 +     */
   1.752 +    private void increment(final Calendar cal) {
   1.753 +        // initialise interval..
   1.754 +        final int calInterval = (getInterval() >= 1) ? getInterval() : 1;
   1.755 +        cal.add(calIncField, calInterval);
   1.756 +    }
   1.757 +
   1.758 +    /**
   1.759 +     * Returns a list of possible dates generated from the applicable BY* rules, using the specified date as a seed.
   1.760 +     * @param date the seed date
   1.761 +     * @param value the type of date list to return
   1.762 +     * @return a DateList
   1.763 +     */
   1.764 +    private DateList getCandidates(final Date date, final Value value) {
   1.765 +        DateList dates = new DateList(value);
   1.766 +        if (date instanceof DateTime) {
   1.767 +            if (((DateTime) date).isUtc()) {
   1.768 +                dates.setUtc(true);
   1.769 +            }
   1.770 +            else {
   1.771 +                dates.setTimeZone(((DateTime) date).getTimeZone());
   1.772 +            }
   1.773 +        }
   1.774 +        dates.add(date);
   1.775 +        dates = getMonthVariants(dates);
   1.776 +        // debugging..
   1.777 +        if (log.isDebugEnabled()) {
   1.778 +            log.debug("Dates after BYMONTH processing: " + dates);
   1.779 +        }
   1.780 +        dates = getWeekNoVariants(dates);
   1.781 +        // debugging..
   1.782 +        if (log.isDebugEnabled()) {
   1.783 +            log.debug("Dates after BYWEEKNO processing: " + dates);
   1.784 +        }
   1.785 +        dates = getYearDayVariants(dates);
   1.786 +        // debugging..
   1.787 +        if (log.isDebugEnabled()) {
   1.788 +            log.debug("Dates after BYYEARDAY processing: " + dates);
   1.789 +        }
   1.790 +        dates = getMonthDayVariants(dates);
   1.791 +        // debugging..
   1.792 +        if (log.isDebugEnabled()) {
   1.793 +            log.debug("Dates after BYMONTHDAY processing: " + dates);
   1.794 +        }
   1.795 +        dates = getDayVariants(dates);
   1.796 +        // debugging..
   1.797 +        if (log.isDebugEnabled()) {
   1.798 +            log.debug("Dates after BYDAY processing: " + dates);
   1.799 +        }
   1.800 +        dates = getHourVariants(dates);
   1.801 +        // debugging..
   1.802 +        if (log.isDebugEnabled()) {
   1.803 +            log.debug("Dates after BYHOUR processing: " + dates);
   1.804 +        }
   1.805 +        dates = getMinuteVariants(dates);
   1.806 +        // debugging..
   1.807 +        if (log.isDebugEnabled()) {
   1.808 +            log.debug("Dates after BYMINUTE processing: " + dates);
   1.809 +        }
   1.810 +        dates = getSecondVariants(dates);
   1.811 +        // debugging..
   1.812 +        if (log.isDebugEnabled()) {
   1.813 +            log.debug("Dates after BYSECOND processing: " + dates);
   1.814 +        }
   1.815 +        dates = applySetPosRules(dates);
   1.816 +        // debugging..
   1.817 +        if (log.isDebugEnabled()) {
   1.818 +            log.debug("Dates after SETPOS processing: " + dates);
   1.819 +        }
   1.820 +        return dates;
   1.821 +    }
   1.822 +
   1.823 +    /**
   1.824 +     * Applies BYSETPOS rules to <code>dates</code>. Valid positions are from 1 to the size of the date list. Invalid
   1.825 +     * positions are ignored.
   1.826 +     * @param dates
   1.827 +     */
   1.828 +    private DateList applySetPosRules(final DateList dates) {
   1.829 +        // return if no SETPOS rules specified..
   1.830 +        if (getSetPosList().isEmpty()) {
   1.831 +            return dates;
   1.832 +        }
   1.833 +        // sort the list before processing..
   1.834 +        Collections.sort(dates);
   1.835 +        final DateList setPosDates = getDateListInstance(dates);
   1.836 +        final int size = dates.size();
   1.837 +        for (final Iterator i = getSetPosList().iterator(); i.hasNext();) {
   1.838 +            final Integer setPos = (Integer) i.next();
   1.839 +            final int pos = setPos.intValue();
   1.840 +            if (pos > 0 && pos <= size) {
   1.841 +                setPosDates.add(dates.get(pos - 1));
   1.842 +            }
   1.843 +            else if (pos < 0 && pos >= -size) {
   1.844 +                setPosDates.add(dates.get(size + pos));
   1.845 +            }
   1.846 +        }
   1.847 +        return setPosDates;
   1.848 +    }
   1.849 +
   1.850 +    /**
   1.851 +     * Applies BYMONTH rules specified in this Recur instance to the specified date list. If no BYMONTH rules are
   1.852 +     * specified the date list is returned unmodified.
   1.853 +     * @param dates
   1.854 +     * @return
   1.855 +     */
   1.856 +    private DateList getMonthVariants(final DateList dates) {
   1.857 +        if (getMonthList().isEmpty()) {
   1.858 +            return dates;
   1.859 +        }
   1.860 +        final DateList monthlyDates = getDateListInstance(dates);
   1.861 +        for (final Iterator i = dates.iterator(); i.hasNext();) {
   1.862 +            final Date date = (Date) i.next();
   1.863 +            final Calendar cal = Dates.getCalendarInstance(date);
   1.864 +            cal.setTime(date);
   1.865 +            for (final Iterator j = getMonthList().iterator(); j.hasNext();) {
   1.866 +                final Integer month = (Integer) j.next();
   1.867 +                // Java months are zero-based..
   1.868 +//                cal.set(Calendar.MONTH, month.intValue() - 1);
   1.869 +                cal.roll(Calendar.MONTH, (month.intValue() - 1) - cal.get(Calendar.MONTH));
   1.870 +                monthlyDates.add(Dates.getInstance(cal.getTime(), monthlyDates.getType()));
   1.871 +            }
   1.872 +        }
   1.873 +        return monthlyDates;
   1.874 +    }
   1.875 +
   1.876 +    /**
   1.877 +     * Applies BYWEEKNO rules specified in this Recur instance to the specified date list. If no BYWEEKNO rules are
   1.878 +     * specified the date list is returned unmodified.
   1.879 +     * @param dates
   1.880 +     * @return
   1.881 +     */
   1.882 +    private DateList getWeekNoVariants(final DateList dates) {
   1.883 +        if (getWeekNoList().isEmpty()) {
   1.884 +            return dates;
   1.885 +        }
   1.886 +        final DateList weekNoDates = getDateListInstance(dates);
   1.887 +        for (final Iterator i = dates.iterator(); i.hasNext();) {
   1.888 +            final Date date = (Date) i.next();
   1.889 +            final Calendar cal = Dates.getCalendarInstance(date);
   1.890 +            cal.setTime(date);
   1.891 +            for (final Iterator j = getWeekNoList().iterator(); j.hasNext();) {
   1.892 +                final Integer weekNo = (Integer) j.next();
   1.893 +                cal.set(Calendar.WEEK_OF_YEAR, Dates.getAbsWeekNo(cal.getTime(), weekNo.intValue()));
   1.894 +                weekNoDates.add(Dates.getInstance(cal.getTime(), weekNoDates.getType()));
   1.895 +            }
   1.896 +        }
   1.897 +        return weekNoDates;
   1.898 +    }
   1.899 +
   1.900 +    /**
   1.901 +     * Applies BYYEARDAY rules specified in this Recur instance to the specified date list. If no BYYEARDAY rules are
   1.902 +     * specified the date list is returned unmodified.
   1.903 +     * @param dates
   1.904 +     * @return
   1.905 +     */
   1.906 +    private DateList getYearDayVariants(final DateList dates) {
   1.907 +        if (getYearDayList().isEmpty()) {
   1.908 +            return dates;
   1.909 +        }
   1.910 +        final DateList yearDayDates = getDateListInstance(dates);
   1.911 +        for (final Iterator i = dates.iterator(); i.hasNext();) {
   1.912 +            final Date date = (Date) i.next();
   1.913 +            final Calendar cal = Dates.getCalendarInstance(date);
   1.914 +            cal.setTime(date);
   1.915 +            for (final Iterator j = getYearDayList().iterator(); j.hasNext();) {
   1.916 +                final Integer yearDay = (Integer) j.next();
   1.917 +                cal.set(Calendar.DAY_OF_YEAR, Dates.getAbsYearDay(cal.getTime(), yearDay.intValue()));
   1.918 +                yearDayDates.add(Dates.getInstance(cal.getTime(), yearDayDates.getType()));
   1.919 +            }
   1.920 +        }
   1.921 +        return yearDayDates;
   1.922 +    }
   1.923 +
   1.924 +    /**
   1.925 +     * Applies BYMONTHDAY rules specified in this Recur instance to the specified date list. If no BYMONTHDAY rules are
   1.926 +     * specified the date list is returned unmodified.
   1.927 +     * @param dates
   1.928 +     * @return
   1.929 +     */
   1.930 +    private DateList getMonthDayVariants(final DateList dates) {
   1.931 +        if (getMonthDayList().isEmpty()) {
   1.932 +            return dates;
   1.933 +        }
   1.934 +        final DateList monthDayDates = getDateListInstance(dates);
   1.935 +        for (final Iterator i = dates.iterator(); i.hasNext();) {
   1.936 +            final Date date = (Date) i.next();
   1.937 +            final Calendar cal = Dates.getCalendarInstance(date);
   1.938 +            cal.setLenient(false);
   1.939 +            cal.setTime(date);
   1.940 +            for (final Iterator j = getMonthDayList().iterator(); j.hasNext();) {
   1.941 +                final Integer monthDay = (Integer) j.next();
   1.942 +                try {
   1.943 +                    cal.set(Calendar.DAY_OF_MONTH, Dates.getAbsMonthDay(cal.getTime(), monthDay.intValue()));
   1.944 +                    monthDayDates.add(Dates.getInstance(cal.getTime(), monthDayDates.getType()));
   1.945 +                }
   1.946 +                catch (IllegalArgumentException iae) {
   1.947 +                    if (log.isTraceEnabled()) {
   1.948 +                        log.trace("Invalid day of month: " + Dates.getAbsMonthDay(cal
   1.949 +                                .getTime(), monthDay.intValue()));
   1.950 +                    }
   1.951 +                }
   1.952 +            }
   1.953 +        }
   1.954 +        return monthDayDates;
   1.955 +    }
   1.956 +
   1.957 +    /**
   1.958 +     * Applies BYDAY rules specified in this Recur instance to the specified date list. If no BYDAY rules are specified
   1.959 +     * the date list is returned unmodified.
   1.960 +     * @param dates
   1.961 +     * @return
   1.962 +     */
   1.963 +    private DateList getDayVariants(final DateList dates) {
   1.964 +        if (getDayList().isEmpty()) {
   1.965 +            return dates;
   1.966 +        }
   1.967 +        final DateList weekDayDates = getDateListInstance(dates);
   1.968 +        for (final Iterator i = dates.iterator(); i.hasNext();) {
   1.969 +            final Date date = (Date) i.next();
   1.970 +            for (final Iterator j = getDayList().iterator(); j.hasNext();) {
   1.971 +                final WeekDay weekDay = (WeekDay) j.next();
   1.972 +                // if BYYEARDAY or BYMONTHDAY is specified filter existing
   1.973 +                // list..
   1.974 +                if (!getYearDayList().isEmpty() || !getMonthDayList().isEmpty()) {
   1.975 +                    final Calendar cal = Dates.getCalendarInstance(date);
   1.976 +                    cal.setTime(date);
   1.977 +                    if (weekDay.equals(WeekDay.getWeekDay(cal))) {
   1.978 +                        weekDayDates.add(date);
   1.979 +                    }
   1.980 +                }
   1.981 +                else {
   1.982 +                    weekDayDates.addAll(getAbsWeekDays(date, dates.getType(), weekDay));
   1.983 +                }
   1.984 +            }
   1.985 +        }
   1.986 +        return weekDayDates;
   1.987 +    }
   1.988 +
   1.989 +    /**
   1.990 +     * Returns a list of applicable dates corresponding to the specified week day in accordance with the frequency
   1.991 +     * specified by this recurrence rule.
   1.992 +     * @param date
   1.993 +     * @param weekDay
   1.994 +     * @return
   1.995 +     */
   1.996 +    private List getAbsWeekDays(final Date date, final Value type, final WeekDay weekDay) {
   1.997 +        final Calendar cal = Dates.getCalendarInstance(date);
   1.998 +        // default week start is Monday per RFC5545
   1.999 +        int calendarWeekStartDay = Calendar.MONDAY;
  1.1000 +        if (weekStartDay != null) {
  1.1001 +        	calendarWeekStartDay = WeekDay.getCalendarDay(new WeekDay(weekStartDay));
  1.1002 +        }
  1.1003 +        cal.setFirstDayOfWeek(calendarWeekStartDay);
  1.1004 +        cal.setTime(date);
  1.1005 +        
  1.1006 +        final DateList days = new DateList(type);
  1.1007 +        if (date instanceof DateTime) {
  1.1008 +            if (((DateTime) date).isUtc()) {
  1.1009 +                days.setUtc(true);
  1.1010 +            }
  1.1011 +            else {
  1.1012 +                days.setTimeZone(((DateTime) date).getTimeZone());
  1.1013 +            }
  1.1014 +        }
  1.1015 +        final int calDay = WeekDay.getCalendarDay(weekDay);
  1.1016 +        if (calDay == -1) {
  1.1017 +            // a matching weekday cannot be identified..
  1.1018 +            return days;
  1.1019 +        }
  1.1020 +        if (DAILY.equals(getFrequency())) {
  1.1021 +            if (cal.get(Calendar.DAY_OF_WEEK) == calDay) {
  1.1022 +                days.add(Dates.getInstance(cal.getTime(), type));
  1.1023 +            }
  1.1024 +        }
  1.1025 +        else if (WEEKLY.equals(getFrequency()) || !getWeekNoList().isEmpty()) {
  1.1026 +            final int weekNo = cal.get(Calendar.WEEK_OF_YEAR);
  1.1027 +            // construct a list of possible week days..
  1.1028 +            cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
  1.1029 +            while (cal.get(Calendar.DAY_OF_WEEK) != calDay) {
  1.1030 +                cal.add(Calendar.DAY_OF_WEEK, 1);
  1.1031 +            }
  1.1032 +//            final int weekNo = cal.get(Calendar.WEEK_OF_YEAR);
  1.1033 +            if (cal.get(Calendar.WEEK_OF_YEAR) == weekNo) {
  1.1034 +                days.add(Dates.getInstance(cal.getTime(), type));
  1.1035 +//                cal.add(Calendar.DAY_OF_WEEK, Dates.DAYS_PER_WEEK);
  1.1036 +            }
  1.1037 +        }
  1.1038 +        else if (MONTHLY.equals(getFrequency()) || !getMonthList().isEmpty()) {
  1.1039 +            final int month = cal.get(Calendar.MONTH);
  1.1040 +            // construct a list of possible month days..
  1.1041 +            cal.set(Calendar.DAY_OF_MONTH, 1);
  1.1042 +            while (cal.get(Calendar.DAY_OF_WEEK) != calDay) {
  1.1043 +                cal.add(Calendar.DAY_OF_MONTH, 1);
  1.1044 +            }
  1.1045 +            while (cal.get(Calendar.MONTH) == month) {
  1.1046 +                days.add(Dates.getInstance(cal.getTime(), type));
  1.1047 +                cal.add(Calendar.DAY_OF_MONTH, Dates.DAYS_PER_WEEK);
  1.1048 +            }
  1.1049 +        }
  1.1050 +        else if (YEARLY.equals(getFrequency())) {
  1.1051 +            final int year = cal.get(Calendar.YEAR);
  1.1052 +            // construct a list of possible year days..
  1.1053 +            cal.set(Calendar.DAY_OF_YEAR, 1);
  1.1054 +            while (cal.get(Calendar.DAY_OF_WEEK) != calDay) {
  1.1055 +                cal.add(Calendar.DAY_OF_YEAR, 1);
  1.1056 +            }
  1.1057 +            while (cal.get(Calendar.YEAR) == year) {
  1.1058 +                days.add(Dates.getInstance(cal.getTime(), type));
  1.1059 +                cal.add(Calendar.DAY_OF_YEAR, Dates.DAYS_PER_WEEK);
  1.1060 +            }
  1.1061 +        }
  1.1062 +        return getOffsetDates(days, weekDay.getOffset());
  1.1063 +    }
  1.1064 +
  1.1065 +    /**
  1.1066 +     * Returns a single-element sublist containing the element of <code>list</code> at <code>offset</code>. Valid
  1.1067 +     * offsets are from 1 to the size of the list. If an invalid offset is supplied, all elements from <code>list</code>
  1.1068 +     * are added to <code>sublist</code>.
  1.1069 +     * @param list
  1.1070 +     * @param offset
  1.1071 +     * @param sublist
  1.1072 +     */
  1.1073 +    private List getOffsetDates(final DateList dates, final int offset) {
  1.1074 +        if (offset == 0) {
  1.1075 +            return dates;
  1.1076 +        }
  1.1077 +        final List offsetDates = getDateListInstance(dates);
  1.1078 +        final int size = dates.size();
  1.1079 +        if (offset < 0 && offset >= -size) {
  1.1080 +            offsetDates.add(dates.get(size + offset));
  1.1081 +        }
  1.1082 +        else if (offset > 0 && offset <= size) {
  1.1083 +            offsetDates.add(dates.get(offset - 1));
  1.1084 +        }
  1.1085 +        return offsetDates;
  1.1086 +    }
  1.1087 +
  1.1088 +    /**
  1.1089 +     * Applies BYHOUR rules specified in this Recur instance to the specified date list. If no BYHOUR rules are
  1.1090 +     * specified the date list is returned unmodified.
  1.1091 +     * @param dates
  1.1092 +     * @return
  1.1093 +     */
  1.1094 +    private DateList getHourVariants(final DateList dates) {
  1.1095 +        if (getHourList().isEmpty()) {
  1.1096 +            return dates;
  1.1097 +        }
  1.1098 +        final DateList hourlyDates = getDateListInstance(dates);
  1.1099 +        for (final Iterator i = dates.iterator(); i.hasNext();) {
  1.1100 +            final Date date = (Date) i.next();
  1.1101 +            final Calendar cal = Dates.getCalendarInstance(date);
  1.1102 +            cal.setTime(date);
  1.1103 +            for (final Iterator j = getHourList().iterator(); j.hasNext();) {
  1.1104 +                final Integer hour = (Integer) j.next();
  1.1105 +                cal.set(Calendar.HOUR_OF_DAY, hour.intValue());
  1.1106 +                hourlyDates.add(Dates.getInstance(cal.getTime(), hourlyDates.getType()));
  1.1107 +            }
  1.1108 +        }
  1.1109 +        return hourlyDates;
  1.1110 +    }
  1.1111 +
  1.1112 +    /**
  1.1113 +     * Applies BYMINUTE rules specified in this Recur instance to the specified date list. If no BYMINUTE rules are
  1.1114 +     * specified the date list is returned unmodified.
  1.1115 +     * @param dates
  1.1116 +     * @return
  1.1117 +     */
  1.1118 +    private DateList getMinuteVariants(final DateList dates) {
  1.1119 +        if (getMinuteList().isEmpty()) {
  1.1120 +            return dates;
  1.1121 +        }
  1.1122 +        final DateList minutelyDates = getDateListInstance(dates);
  1.1123 +        for (final Iterator i = dates.iterator(); i.hasNext();) {
  1.1124 +            final Date date = (Date) i.next();
  1.1125 +            final Calendar cal = Dates.getCalendarInstance(date);
  1.1126 +            cal.setTime(date);
  1.1127 +            for (final Iterator j = getMinuteList().iterator(); j.hasNext();) {
  1.1128 +                final Integer minute = (Integer) j.next();
  1.1129 +                cal.set(Calendar.MINUTE, minute.intValue());
  1.1130 +                minutelyDates.add(Dates.getInstance(cal.getTime(), minutelyDates.getType()));
  1.1131 +            }
  1.1132 +        }
  1.1133 +        return minutelyDates;
  1.1134 +    }
  1.1135 +
  1.1136 +    /**
  1.1137 +     * Applies BYSECOND rules specified in this Recur instance to the specified date list. If no BYSECOND rules are
  1.1138 +     * specified the date list is returned unmodified.
  1.1139 +     * @param dates
  1.1140 +     * @return
  1.1141 +     */
  1.1142 +    private DateList getSecondVariants(final DateList dates) {
  1.1143 +        if (getSecondList().isEmpty()) {
  1.1144 +            return dates;
  1.1145 +        }
  1.1146 +        final DateList secondlyDates = getDateListInstance(dates);
  1.1147 +        for (final Iterator i = dates.iterator(); i.hasNext();) {
  1.1148 +            final Date date = (Date) i.next();
  1.1149 +            final Calendar cal = Dates.getCalendarInstance(date);
  1.1150 +            cal.setTime(date);
  1.1151 +            for (final Iterator j = getSecondList().iterator(); j.hasNext();) {
  1.1152 +                final Integer second = (Integer) j.next();
  1.1153 +                cal.set(Calendar.SECOND, second.intValue());
  1.1154 +                secondlyDates.add(Dates.getInstance(cal.getTime(), secondlyDates.getType()));
  1.1155 +            }
  1.1156 +        }
  1.1157 +        return secondlyDates;
  1.1158 +    }
  1.1159 +
  1.1160 +    private void validateFrequency() {
  1.1161 +        if (frequency == null) {
  1.1162 +            throw new IllegalArgumentException(
  1.1163 +                    "A recurrence rule MUST contain a FREQ rule part.");
  1.1164 +        }
  1.1165 +        if (SECONDLY.equals(getFrequency())) {
  1.1166 +            calIncField = Calendar.SECOND;
  1.1167 +        }
  1.1168 +        else if (MINUTELY.equals(getFrequency())) {
  1.1169 +            calIncField = Calendar.MINUTE;
  1.1170 +        }
  1.1171 +        else if (HOURLY.equals(getFrequency())) {
  1.1172 +            calIncField = Calendar.HOUR_OF_DAY;
  1.1173 +        }
  1.1174 +        else if (DAILY.equals(getFrequency())) {
  1.1175 +            calIncField = Calendar.DAY_OF_YEAR;
  1.1176 +        }
  1.1177 +        else if (WEEKLY.equals(getFrequency())) {
  1.1178 +            calIncField = Calendar.WEEK_OF_YEAR;
  1.1179 +        }
  1.1180 +        else if (MONTHLY.equals(getFrequency())) {
  1.1181 +            calIncField = Calendar.MONTH;
  1.1182 +        }
  1.1183 +        else if (YEARLY.equals(getFrequency())) {
  1.1184 +            calIncField = Calendar.YEAR;
  1.1185 +        }
  1.1186 +        else {
  1.1187 +            throw new IllegalArgumentException("Invalid FREQ rule part '"
  1.1188 +                    + frequency + "' in recurrence rule");
  1.1189 +        }
  1.1190 +    }
  1.1191 +
  1.1192 +    /**
  1.1193 +     * @param count The count to set.
  1.1194 +     */
  1.1195 +    public final void setCount(final int count) {
  1.1196 +        this.count = count;
  1.1197 +        this.until = null;
  1.1198 +    }
  1.1199 +
  1.1200 +    /**
  1.1201 +     * @param frequency The frequency to set.
  1.1202 +     */
  1.1203 +    public final void setFrequency(final String frequency) {
  1.1204 +        this.frequency = frequency;
  1.1205 +        validateFrequency();
  1.1206 +    }
  1.1207 +
  1.1208 +    /**
  1.1209 +     * @param interval The interval to set.
  1.1210 +     */
  1.1211 +    public final void setInterval(final int interval) {
  1.1212 +        this.interval = interval;
  1.1213 +    }
  1.1214 +
  1.1215 +    /**
  1.1216 +     * @param until The until to set.
  1.1217 +     */
  1.1218 +    public final void setUntil(final Date until) {
  1.1219 +        this.until = until;
  1.1220 +        this.count = -1;
  1.1221 +    }
  1.1222 +    
  1.1223 +    /**
  1.1224 +     * @param stream
  1.1225 +     * @throws IOException
  1.1226 +     * @throws ClassNotFoundException
  1.1227 +     */
  1.1228 +    private void readObject(final java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException {
  1.1229 +        stream.defaultReadObject();
  1.1230 +        log = LogFactory.getLog(Recur.class);
  1.1231 +    }
  1.1232 +    
  1.1233 +    /**
  1.1234 +     * Instantiate a new datelist with the same type, timezone and utc settings
  1.1235 +     *  as the origList.
  1.1236 +     * @param origList
  1.1237 +     * @return a new empty list.
  1.1238 +     */
  1.1239 +    private static final DateList getDateListInstance(final DateList origList) {
  1.1240 +        final DateList list = new DateList(origList.getType());
  1.1241 +        if (origList.isUtc()) {
  1.1242 +            list.setUtc(true);
  1.1243 +        } else {
  1.1244 +            list.setTimeZone(origList.getTimeZone());
  1.1245 +        }
  1.1246 +        return list;
  1.1247 +    }
  1.1248 +
  1.1249 +}

mercurial