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 +}