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

Tue, 10 Feb 2015 18:12:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 10 Feb 2015 18:12:00 +0100
changeset 0
fb9019fb1bf7
child 3
73bdfa70b04e
permissions
-rw-r--r--

Import initial revisions of existing project AndroidCaldavSyncAdapater,
forked from upstream repository at 27e8a0f8495c92e0780d450bdf0c7cec77a03a55.

michael@0 1 /**
michael@0 2 * Copyright (c) 2012, Ben Fortuna
michael@0 3 * All rights reserved.
michael@0 4 *
michael@0 5 * Redistribution and use in source and binary forms, with or without
michael@0 6 * modification, are permitted provided that the following conditions
michael@0 7 * are met:
michael@0 8 *
michael@0 9 * o Redistributions of source code must retain the above copyright
michael@0 10 * notice, this list of conditions and the following disclaimer.
michael@0 11 *
michael@0 12 * o Redistributions in binary form must reproduce the above copyright
michael@0 13 * notice, this list of conditions and the following disclaimer in the
michael@0 14 * documentation and/or other materials provided with the distribution.
michael@0 15 *
michael@0 16 * o Neither the name of Ben Fortuna nor the names of any other contributors
michael@0 17 * may be used to endorse or promote products derived from this software
michael@0 18 * without specific prior written permission.
michael@0 19 *
michael@0 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
michael@0 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
michael@0 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
michael@0 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
michael@0 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
michael@0 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
michael@0 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
michael@0 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
michael@0 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
michael@0 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
michael@0 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
michael@0 31 */
michael@0 32 package net.fortuna.ical4j.model;
michael@0 33
michael@0 34 import java.io.IOException;
michael@0 35 import java.io.Serializable;
michael@0 36 import java.text.ParseException;
michael@0 37 import java.util.Calendar;
michael@0 38 import java.util.Collections;
michael@0 39 import java.util.HashMap;
michael@0 40 import java.util.Iterator;
michael@0 41 import java.util.List;
michael@0 42 import java.util.Map;
michael@0 43 import java.util.NoSuchElementException;
michael@0 44 import java.util.StringTokenizer;
michael@0 45
michael@0 46 import net.fortuna.ical4j.model.parameter.Value;
michael@0 47 import net.fortuna.ical4j.util.Configurator;
michael@0 48 import net.fortuna.ical4j.util.Dates;
michael@0 49
michael@0 50 import org.apache.commons.logging.Log;
michael@0 51 import org.apache.commons.logging.LogFactory;
michael@0 52
michael@0 53 /**
michael@0 54 * $Id$ [18-Apr-2004]
michael@0 55 *
michael@0 56 * Defines a recurrence.
michael@0 57 * @version 2.0
michael@0 58 * @author Ben Fortuna
michael@0 59 */
michael@0 60 public class Recur implements Serializable {
michael@0 61
michael@0 62 private static final long serialVersionUID = -7333226591784095142L;
michael@0 63
michael@0 64 private static final String FREQ = "FREQ";
michael@0 65
michael@0 66 private static final String UNTIL = "UNTIL";
michael@0 67
michael@0 68 private static final String COUNT = "COUNT";
michael@0 69
michael@0 70 private static final String INTERVAL = "INTERVAL";
michael@0 71
michael@0 72 private static final String BYSECOND = "BYSECOND";
michael@0 73
michael@0 74 private static final String BYMINUTE = "BYMINUTE";
michael@0 75
michael@0 76 private static final String BYHOUR = "BYHOUR";
michael@0 77
michael@0 78 private static final String BYDAY = "BYDAY";
michael@0 79
michael@0 80 private static final String BYMONTHDAY = "BYMONTHDAY";
michael@0 81
michael@0 82 private static final String BYYEARDAY = "BYYEARDAY";
michael@0 83
michael@0 84 private static final String BYWEEKNO = "BYWEEKNO";
michael@0 85
michael@0 86 private static final String BYMONTH = "BYMONTH";
michael@0 87
michael@0 88 private static final String BYSETPOS = "BYSETPOS";
michael@0 89
michael@0 90 private static final String WKST = "WKST";
michael@0 91
michael@0 92 /**
michael@0 93 * Second frequency resolution.
michael@0 94 */
michael@0 95 public static final String SECONDLY = "SECONDLY";
michael@0 96
michael@0 97 /**
michael@0 98 * Minute frequency resolution.
michael@0 99 */
michael@0 100 public static final String MINUTELY = "MINUTELY";
michael@0 101
michael@0 102 /**
michael@0 103 * Hour frequency resolution.
michael@0 104 */
michael@0 105 public static final String HOURLY = "HOURLY";
michael@0 106
michael@0 107 /**
michael@0 108 * Day frequency resolution.
michael@0 109 */
michael@0 110 public static final String DAILY = "DAILY";
michael@0 111
michael@0 112 /**
michael@0 113 * Week frequency resolution.
michael@0 114 */
michael@0 115 public static final String WEEKLY = "WEEKLY";
michael@0 116
michael@0 117 /**
michael@0 118 * Month frequency resolution.
michael@0 119 */
michael@0 120 public static final String MONTHLY = "MONTHLY";
michael@0 121
michael@0 122 /**
michael@0 123 * Year frequency resolution.
michael@0 124 */
michael@0 125 public static final String YEARLY = "YEARLY";
michael@0 126
michael@0 127 /**
michael@0 128 * When calculating dates matching this recur ({@code getDates()} or {@code getNextDate}),
michael@0 129 * this property defines the maximum number of attempt to find a matching date by
michael@0 130 * incrementing the seed.
michael@0 131 * <p>The default value is 1000. A value of -1 corresponds to no maximum.</p>
michael@0 132 */
michael@0 133 public static final String KEY_MAX_INCREMENT_COUNT = "net.fortuna.ical4j.recur.maxincrementcount";
michael@0 134
michael@0 135 private static int maxIncrementCount;
michael@0 136 static {
michael@0 137 final String value = Configurator.getProperty(KEY_MAX_INCREMENT_COUNT);
michael@0 138 if (value != null && value.length() > 0) {
michael@0 139 maxIncrementCount = Integer.parseInt(value);
michael@0 140 } else {
michael@0 141 maxIncrementCount = 1000;
michael@0 142 }
michael@0 143 }
michael@0 144
michael@0 145 private transient Log log = LogFactory.getLog(Recur.class);
michael@0 146
michael@0 147 private String frequency;
michael@0 148
michael@0 149 private Date until;
michael@0 150
michael@0 151 private int count = -1;
michael@0 152
michael@0 153 private int interval = -1;
michael@0 154
michael@0 155 private NumberList secondList;
michael@0 156
michael@0 157 private NumberList minuteList;
michael@0 158
michael@0 159 private NumberList hourList;
michael@0 160
michael@0 161 private WeekDayList dayList;
michael@0 162
michael@0 163 private NumberList monthDayList;
michael@0 164
michael@0 165 private NumberList yearDayList;
michael@0 166
michael@0 167 private NumberList weekNoList;
michael@0 168
michael@0 169 private NumberList monthList;
michael@0 170
michael@0 171 private NumberList setPosList;
michael@0 172
michael@0 173 private String weekStartDay;
michael@0 174
michael@0 175 private Map experimentalValues = new HashMap();
michael@0 176
michael@0 177 // Calendar field we increment based on frequency.
michael@0 178 private int calIncField;
michael@0 179
michael@0 180 /**
michael@0 181 * Default constructor.
michael@0 182 */
michael@0 183 public Recur() {
michael@0 184 }
michael@0 185
michael@0 186 /**
michael@0 187 * Constructs a new instance from the specified string value.
michael@0 188 * @param aValue a string representation of a recurrence.
michael@0 189 * @throws ParseException thrown when the specified string contains an invalid representation of an UNTIL date value
michael@0 190 */
michael@0 191 public Recur(final String aValue) throws ParseException {
michael@0 192 final StringTokenizer t = new StringTokenizer(aValue, ";=");
michael@0 193 while (t.hasMoreTokens()) {
michael@0 194 final String token = t.nextToken();
michael@0 195 if (FREQ.equals(token)) {
michael@0 196 frequency = nextToken(t, token);
michael@0 197 }
michael@0 198 else if (UNTIL.equals(token)) {
michael@0 199 final String untilString = nextToken(t, token);
michael@0 200 if (untilString != null && untilString.indexOf("T") >= 0) {
michael@0 201 until = new DateTime(untilString);
michael@0 202 // UNTIL must be specified in UTC time..
michael@0 203 ((DateTime) until).setUtc(true);
michael@0 204 }
michael@0 205 else {
michael@0 206 until = new Date(untilString);
michael@0 207 }
michael@0 208 }
michael@0 209 else if (COUNT.equals(token)) {
michael@0 210 count = Integer.parseInt(nextToken(t, token));
michael@0 211 }
michael@0 212 else if (INTERVAL.equals(token)) {
michael@0 213 interval = Integer.parseInt(nextToken(t, token));
michael@0 214 }
michael@0 215 else if (BYSECOND.equals(token)) {
michael@0 216 secondList = new NumberList(nextToken(t, token), 0, 59, false);
michael@0 217 }
michael@0 218 else if (BYMINUTE.equals(token)) {
michael@0 219 minuteList = new NumberList(nextToken(t, token), 0, 59, false);
michael@0 220 }
michael@0 221 else if (BYHOUR.equals(token)) {
michael@0 222 hourList = new NumberList(nextToken(t, token), 0, 23, false);
michael@0 223 }
michael@0 224 else if (BYDAY.equals(token)) {
michael@0 225 dayList = new WeekDayList(nextToken(t, token));
michael@0 226 }
michael@0 227 else if (BYMONTHDAY.equals(token)) {
michael@0 228 monthDayList = new NumberList(nextToken(t, token), 1, 31, true);
michael@0 229 }
michael@0 230 else if (BYYEARDAY.equals(token)) {
michael@0 231 yearDayList = new NumberList(nextToken(t, token), 1, 366, true);
michael@0 232 }
michael@0 233 else if (BYWEEKNO.equals(token)) {
michael@0 234 weekNoList = new NumberList(nextToken(t, token), 1, 53, true);
michael@0 235 }
michael@0 236 else if (BYMONTH.equals(token)) {
michael@0 237 monthList = new NumberList(nextToken(t, token), 1, 12, false);
michael@0 238 }
michael@0 239 else if (BYSETPOS.equals(token)) {
michael@0 240 setPosList = new NumberList(nextToken(t, token), 1, 366, true);
michael@0 241 }
michael@0 242 else if (WKST.equals(token)) {
michael@0 243 weekStartDay = nextToken(t, token);
michael@0 244 }
michael@0 245 // assume experimental value..
michael@0 246 else {
michael@0 247 experimentalValues.put(token, nextToken(t, token));
michael@0 248 }
michael@0 249 }
michael@0 250 validateFrequency();
michael@0 251 }
michael@0 252
michael@0 253 private String nextToken(StringTokenizer t, String lastToken) {
michael@0 254 try {
michael@0 255 return t.nextToken();
michael@0 256 }
michael@0 257 catch (NoSuchElementException e) {
michael@0 258 throw new IllegalArgumentException("Missing expected token, last token: " + lastToken);
michael@0 259 }
michael@0 260 }
michael@0 261
michael@0 262 /**
michael@0 263 * @param frequency a recurrence frequency string
michael@0 264 * @param until maximum recurrence date
michael@0 265 */
michael@0 266 public Recur(final String frequency, final Date until) {
michael@0 267 this.frequency = frequency;
michael@0 268 this.until = until;
michael@0 269 validateFrequency();
michael@0 270 }
michael@0 271
michael@0 272 /**
michael@0 273 * @param frequency a recurrence frequency string
michael@0 274 * @param count maximum recurrence count
michael@0 275 */
michael@0 276 public Recur(final String frequency, final int count) {
michael@0 277 this.frequency = frequency;
michael@0 278 this.count = count;
michael@0 279 validateFrequency();
michael@0 280 }
michael@0 281
michael@0 282 /**
michael@0 283 * @return Returns the dayList.
michael@0 284 */
michael@0 285 public final WeekDayList getDayList() {
michael@0 286 if (dayList == null) {
michael@0 287 dayList = new WeekDayList();
michael@0 288 }
michael@0 289 return dayList;
michael@0 290 }
michael@0 291
michael@0 292 /**
michael@0 293 * @return Returns the hourList.
michael@0 294 */
michael@0 295 public final NumberList getHourList() {
michael@0 296 if (hourList == null) {
michael@0 297 hourList = new NumberList(0, 23, false);
michael@0 298 }
michael@0 299 return hourList;
michael@0 300 }
michael@0 301
michael@0 302 /**
michael@0 303 * @return Returns the minuteList.
michael@0 304 */
michael@0 305 public final NumberList getMinuteList() {
michael@0 306 if (minuteList == null) {
michael@0 307 minuteList = new NumberList(0, 59, false);
michael@0 308 }
michael@0 309 return minuteList;
michael@0 310 }
michael@0 311
michael@0 312 /**
michael@0 313 * @return Returns the monthDayList.
michael@0 314 */
michael@0 315 public final NumberList getMonthDayList() {
michael@0 316 if (monthDayList == null) {
michael@0 317 monthDayList = new NumberList(1, 31, true);
michael@0 318 }
michael@0 319 return monthDayList;
michael@0 320 }
michael@0 321
michael@0 322 /**
michael@0 323 * @return Returns the monthList.
michael@0 324 */
michael@0 325 public final NumberList getMonthList() {
michael@0 326 if (monthList == null) {
michael@0 327 monthList = new NumberList(1, 12, false);
michael@0 328 }
michael@0 329 return monthList;
michael@0 330 }
michael@0 331
michael@0 332 /**
michael@0 333 * @return Returns the secondList.
michael@0 334 */
michael@0 335 public final NumberList getSecondList() {
michael@0 336 if (secondList == null) {
michael@0 337 secondList = new NumberList(0, 59, false);
michael@0 338 }
michael@0 339 return secondList;
michael@0 340 }
michael@0 341
michael@0 342 /**
michael@0 343 * @return Returns the setPosList.
michael@0 344 */
michael@0 345 public final NumberList getSetPosList() {
michael@0 346 if (setPosList == null) {
michael@0 347 setPosList = new NumberList(1, 366, true);
michael@0 348 }
michael@0 349 return setPosList;
michael@0 350 }
michael@0 351
michael@0 352 /**
michael@0 353 * @return Returns the weekNoList.
michael@0 354 */
michael@0 355 public final NumberList getWeekNoList() {
michael@0 356 if (weekNoList == null) {
michael@0 357 weekNoList = new NumberList(1, 53, true);
michael@0 358 }
michael@0 359 return weekNoList;
michael@0 360 }
michael@0 361
michael@0 362 /**
michael@0 363 * @return Returns the yearDayList.
michael@0 364 */
michael@0 365 public final NumberList getYearDayList() {
michael@0 366 if (yearDayList == null) {
michael@0 367 yearDayList = new NumberList(1, 366, true);
michael@0 368 }
michael@0 369 return yearDayList;
michael@0 370 }
michael@0 371
michael@0 372 /**
michael@0 373 * @return Returns the count or -1 if the rule does not have a count.
michael@0 374 */
michael@0 375 public final int getCount() {
michael@0 376 return count;
michael@0 377 }
michael@0 378
michael@0 379 /**
michael@0 380 * @return Returns the experimentalValues.
michael@0 381 */
michael@0 382 public final Map getExperimentalValues() {
michael@0 383 return experimentalValues;
michael@0 384 }
michael@0 385
michael@0 386 /**
michael@0 387 * @return Returns the frequency.
michael@0 388 */
michael@0 389 public final String getFrequency() {
michael@0 390 return frequency;
michael@0 391 }
michael@0 392
michael@0 393 /**
michael@0 394 * @return Returns the interval or -1 if the rule does not have an interval defined.
michael@0 395 */
michael@0 396 public final int getInterval() {
michael@0 397 return interval;
michael@0 398 }
michael@0 399
michael@0 400 /**
michael@0 401 * @return Returns the until or null if there is none.
michael@0 402 */
michael@0 403 public final Date getUntil() {
michael@0 404 return until;
michael@0 405 }
michael@0 406
michael@0 407 /**
michael@0 408 * @return Returns the weekStartDay or null if there is none.
michael@0 409 */
michael@0 410 public final String getWeekStartDay() {
michael@0 411 return weekStartDay;
michael@0 412 }
michael@0 413
michael@0 414 /**
michael@0 415 * @param weekStartDay The weekStartDay to set.
michael@0 416 */
michael@0 417 public final void setWeekStartDay(final String weekStartDay) {
michael@0 418 this.weekStartDay = weekStartDay;
michael@0 419 }
michael@0 420
michael@0 421 /**
michael@0 422 * {@inheritDoc}
michael@0 423 */
michael@0 424 public final String toString() {
michael@0 425 final StringBuffer b = new StringBuffer();
michael@0 426 b.append(FREQ);
michael@0 427 b.append('=');
michael@0 428 b.append(frequency);
michael@0 429 if (weekStartDay != null) {
michael@0 430 b.append(';');
michael@0 431 b.append(WKST);
michael@0 432 b.append('=');
michael@0 433 b.append(weekStartDay);
michael@0 434 }
michael@0 435 if (until != null) {
michael@0 436 b.append(';');
michael@0 437 b.append(UNTIL);
michael@0 438 b.append('=');
michael@0 439 // Note: date-time representations should always be in UTC time.
michael@0 440 b.append(until);
michael@0 441 }
michael@0 442 if (count >= 1) {
michael@0 443 b.append(';');
michael@0 444 b.append(COUNT);
michael@0 445 b.append('=');
michael@0 446 b.append(count);
michael@0 447 }
michael@0 448 if (interval >= 1) {
michael@0 449 b.append(';');
michael@0 450 b.append(INTERVAL);
michael@0 451 b.append('=');
michael@0 452 b.append(interval);
michael@0 453 }
michael@0 454 if (!getMonthList().isEmpty()) {
michael@0 455 b.append(';');
michael@0 456 b.append(BYMONTH);
michael@0 457 b.append('=');
michael@0 458 b.append(monthList);
michael@0 459 }
michael@0 460 if (!getWeekNoList().isEmpty()) {
michael@0 461 b.append(';');
michael@0 462 b.append(BYWEEKNO);
michael@0 463 b.append('=');
michael@0 464 b.append(weekNoList);
michael@0 465 }
michael@0 466 if (!getYearDayList().isEmpty()) {
michael@0 467 b.append(';');
michael@0 468 b.append(BYYEARDAY);
michael@0 469 b.append('=');
michael@0 470 b.append(yearDayList);
michael@0 471 }
michael@0 472 if (!getMonthDayList().isEmpty()) {
michael@0 473 b.append(';');
michael@0 474 b.append(BYMONTHDAY);
michael@0 475 b.append('=');
michael@0 476 b.append(monthDayList);
michael@0 477 }
michael@0 478 if (!getDayList().isEmpty()) {
michael@0 479 b.append(';');
michael@0 480 b.append(BYDAY);
michael@0 481 b.append('=');
michael@0 482 b.append(dayList);
michael@0 483 }
michael@0 484 if (!getHourList().isEmpty()) {
michael@0 485 b.append(';');
michael@0 486 b.append(BYHOUR);
michael@0 487 b.append('=');
michael@0 488 b.append(hourList);
michael@0 489 }
michael@0 490 if (!getMinuteList().isEmpty()) {
michael@0 491 b.append(';');
michael@0 492 b.append(BYMINUTE);
michael@0 493 b.append('=');
michael@0 494 b.append(minuteList);
michael@0 495 }
michael@0 496 if (!getSecondList().isEmpty()) {
michael@0 497 b.append(';');
michael@0 498 b.append(BYSECOND);
michael@0 499 b.append('=');
michael@0 500 b.append(secondList);
michael@0 501 }
michael@0 502 if (!getSetPosList().isEmpty()) {
michael@0 503 b.append(';');
michael@0 504 b.append(BYSETPOS);
michael@0 505 b.append('=');
michael@0 506 b.append(setPosList);
michael@0 507 }
michael@0 508 return b.toString();
michael@0 509 }
michael@0 510
michael@0 511 /**
michael@0 512 * Returns a list of start dates in the specified period represented by this recur. Any date fields not specified by
michael@0 513 * this recur are retained from the period start, and as such you should ensure the period start is initialised
michael@0 514 * correctly.
michael@0 515 * @param periodStart the start of the period
michael@0 516 * @param periodEnd the end of the period
michael@0 517 * @param value the type of dates to generate (i.e. date/date-time)
michael@0 518 * @return a list of dates
michael@0 519 */
michael@0 520 public final DateList getDates(final Date periodStart,
michael@0 521 final Date periodEnd, final Value value) {
michael@0 522 return getDates(periodStart, periodStart, periodEnd, value, -1);
michael@0 523 }
michael@0 524
michael@0 525 /**
michael@0 526 * Convenience method for retrieving recurrences in a specified period.
michael@0 527 * @param seed a seed date for generating recurrence instances
michael@0 528 * @param period the period of returned recurrence dates
michael@0 529 * @param value type of dates to generate
michael@0 530 * @return a list of dates
michael@0 531 */
michael@0 532 public final DateList getDates(final Date seed, final Period period,
michael@0 533 final Value value) {
michael@0 534 return getDates(seed, period.getStart(), period.getEnd(), value, -1);
michael@0 535 }
michael@0 536
michael@0 537 /**
michael@0 538 * Returns a list of start dates in the specified period represented by this recur. This method includes a base date
michael@0 539 * argument, which indicates the start of the fist occurrence of this recurrence. The base date is used to inject
michael@0 540 * default values to return a set of dates in the correct format. For example, if the search start date (start) is
michael@0 541 * Wed, Mar 23, 12:19PM, but the recurrence is Mon - Fri, 9:00AM - 5:00PM, the start dates returned should all be at
michael@0 542 * 9:00AM, and not 12:19PM.
michael@0 543 * @return a list of dates represented by this recur instance
michael@0 544 * @param seed the start date of this Recurrence's first instance
michael@0 545 * @param periodStart the start of the period
michael@0 546 * @param periodEnd the end of the period
michael@0 547 * @param value the type of dates to generate (i.e. date/date-time)
michael@0 548 */
michael@0 549 public final DateList getDates(final Date seed, final Date periodStart,
michael@0 550 final Date periodEnd, final Value value) {
michael@0 551 return getDates(seed, periodStart, periodEnd, value, -1);
michael@0 552 }
michael@0 553
michael@0 554 /**
michael@0 555 * Returns a list of start dates in the specified period represented by this recur. This method includes a base date
michael@0 556 * argument, which indicates the start of the fist occurrence of this recurrence. The base date is used to inject
michael@0 557 * default values to return a set of dates in the correct format. For example, if the search start date (start) is
michael@0 558 * Wed, Mar 23, 12:19PM, but the recurrence is Mon - Fri, 9:00AM - 5:00PM, the start dates returned should all be at
michael@0 559 * 9:00AM, and not 12:19PM.
michael@0 560 * @return a list of dates represented by this recur instance
michael@0 561 * @param seed the start date of this Recurrence's first instance
michael@0 562 * @param periodStart the start of the period
michael@0 563 * @param periodEnd the end of the period
michael@0 564 * @param value the type of dates to generate (i.e. date/date-time)
michael@0 565 * @param maxCount limits the number of instances returned. Up to one years
michael@0 566 * worth extra may be returned. Less than 0 means no limit
michael@0 567 */
michael@0 568 public final DateList getDates(final Date seed, final Date periodStart,
michael@0 569 final Date periodEnd, final Value value,
michael@0 570 final int maxCount) {
michael@0 571
michael@0 572 final DateList dates = new DateList(value);
michael@0 573 if (seed instanceof DateTime) {
michael@0 574 if (((DateTime) seed).isUtc()) {
michael@0 575 dates.setUtc(true);
michael@0 576 }
michael@0 577 else {
michael@0 578 dates.setTimeZone(((DateTime) seed).getTimeZone());
michael@0 579 }
michael@0 580 }
michael@0 581 final Calendar cal = Dates.getCalendarInstance(seed);
michael@0 582 cal.setTime(seed);
michael@0 583
michael@0 584 // optimize the start time for selecting candidates
michael@0 585 // (only applicable where a COUNT is not specified)
michael@0 586 if (getCount() < 1) {
michael@0 587 final Calendar seededCal = (Calendar) cal.clone();
michael@0 588 while (seededCal.getTime().before(periodStart)) {
michael@0 589 cal.setTime(seededCal.getTime());
michael@0 590 increment(seededCal);
michael@0 591 }
michael@0 592 }
michael@0 593
michael@0 594 int invalidCandidateCount = 0;
michael@0 595 int noCandidateIncrementCount = 0;
michael@0 596 Date candidate = null;
michael@0 597 while ((maxCount < 0) || (dates.size() < maxCount)) {
michael@0 598 final Date candidateSeed = Dates.getInstance(cal.getTime(), value);
michael@0 599
michael@0 600 if (getUntil() != null && candidate != null
michael@0 601 && candidate.after(getUntil())) {
michael@0 602
michael@0 603 break;
michael@0 604 }
michael@0 605 if (periodEnd != null && candidate != null
michael@0 606 && candidate.after(periodEnd)) {
michael@0 607
michael@0 608 break;
michael@0 609 }
michael@0 610 if (getCount() >= 1
michael@0 611 && (dates.size() + invalidCandidateCount) >= getCount()) {
michael@0 612
michael@0 613 break;
michael@0 614 }
michael@0 615
michael@0 616 // if (Value.DATE_TIME.equals(value)) {
michael@0 617 if (candidateSeed instanceof DateTime) {
michael@0 618 if (dates.isUtc()) {
michael@0 619 ((DateTime) candidateSeed).setUtc(true);
michael@0 620 }
michael@0 621 else {
michael@0 622 ((DateTime) candidateSeed).setTimeZone(dates.getTimeZone());
michael@0 623 }
michael@0 624 }
michael@0 625
michael@0 626 final DateList candidates = getCandidates(candidateSeed, value);
michael@0 627 if (!candidates.isEmpty()) {
michael@0 628 noCandidateIncrementCount = 0;
michael@0 629 // sort candidates for identifying when UNTIL date is exceeded..
michael@0 630 Collections.sort(candidates);
michael@0 631 for (final Iterator i = candidates.iterator(); i.hasNext();) {
michael@0 632 candidate = (Date) i.next();
michael@0 633 // don't count candidates that occur before the seed date..
michael@0 634 if (!candidate.before(seed)) {
michael@0 635 // candidates exclusive of periodEnd..
michael@0 636 if (candidate.before(periodStart)
michael@0 637 || !candidate.before(periodEnd)) {
michael@0 638 invalidCandidateCount++;
michael@0 639 } else if (getCount() >= 1
michael@0 640 && (dates.size() + invalidCandidateCount) >= getCount()) {
michael@0 641 break;
michael@0 642 } else if (!(getUntil() != null
michael@0 643 && candidate.after(getUntil()))) {
michael@0 644 dates.add(candidate);
michael@0 645 }
michael@0 646 }
michael@0 647 }
michael@0 648 } else {
michael@0 649 noCandidateIncrementCount++;
michael@0 650 if ((maxIncrementCount > 0) && (noCandidateIncrementCount > maxIncrementCount)) {
michael@0 651 break;
michael@0 652 }
michael@0 653 }
michael@0 654 increment(cal);
michael@0 655 }
michael@0 656 // sort final list..
michael@0 657 Collections.sort(dates);
michael@0 658 return dates;
michael@0 659 }
michael@0 660
michael@0 661 /**
michael@0 662 * Returns the the next date of this recurrence given a seed date
michael@0 663 * and start date. The seed date indicates the start of the fist
michael@0 664 * occurrence of this recurrence. The start date is the
michael@0 665 * starting date to search for the next recurrence. Return null
michael@0 666 * if there is no occurrence date after start date.
michael@0 667 * @return the next date in the recurrence series after startDate
michael@0 668 * @param seed the start date of this Recurrence's first instance
michael@0 669 * @param startDate the date to start the search
michael@0 670 */
michael@0 671 public final Date getNextDate(final Date seed, final Date startDate) {
michael@0 672
michael@0 673 final Calendar cal = Dates.getCalendarInstance(seed);
michael@0 674 cal.setTime(seed);
michael@0 675
michael@0 676 // optimize the start time for selecting candidates
michael@0 677 // (only applicable where a COUNT is not specified)
michael@0 678 if (getCount() < 1) {
michael@0 679 final Calendar seededCal = (Calendar) cal.clone();
michael@0 680 while (seededCal.getTime().before(startDate)) {
michael@0 681 cal.setTime(seededCal.getTime());
michael@0 682 increment(seededCal);
michael@0 683 }
michael@0 684 }
michael@0 685
michael@0 686 int invalidCandidateCount = 0;
michael@0 687 int noCandidateIncrementCount = 0;
michael@0 688 Date candidate = null;
michael@0 689 final Value value = seed instanceof DateTime ? Value.DATE_TIME : Value.DATE;
michael@0 690
michael@0 691 while (true) {
michael@0 692 final Date candidateSeed = Dates.getInstance(cal.getTime(), value);
michael@0 693
michael@0 694 if (getUntil() != null && candidate != null && candidate.after(getUntil())) {
michael@0 695 break;
michael@0 696 }
michael@0 697
michael@0 698 if (getCount() > 0 && invalidCandidateCount >= getCount()) {
michael@0 699 break;
michael@0 700 }
michael@0 701
michael@0 702 if (Value.DATE_TIME.equals(value)) {
michael@0 703 if (((DateTime) seed).isUtc()) {
michael@0 704 ((DateTime) candidateSeed).setUtc(true);
michael@0 705 }
michael@0 706 else {
michael@0 707 ((DateTime) candidateSeed).setTimeZone(((DateTime) seed).getTimeZone());
michael@0 708 }
michael@0 709 }
michael@0 710
michael@0 711 final DateList candidates = getCandidates(candidateSeed, value);
michael@0 712 if (!candidates.isEmpty()) {
michael@0 713 noCandidateIncrementCount = 0;
michael@0 714 // sort candidates for identifying when UNTIL date is exceeded..
michael@0 715 Collections.sort(candidates);
michael@0 716
michael@0 717 for (final Iterator i = candidates.iterator(); i.hasNext();) {
michael@0 718 candidate = (Date) i.next();
michael@0 719 // don't count candidates that occur before the seed date..
michael@0 720 if (!candidate.before(seed)) {
michael@0 721 // Candidate must be after startDate because
michael@0 722 // we want the NEXT occurrence
michael@0 723 if (!candidate.after(startDate)) {
michael@0 724 invalidCandidateCount++;
michael@0 725 } else if (getCount() > 0
michael@0 726 && invalidCandidateCount >= getCount()) {
michael@0 727 break;
michael@0 728 } else if (!(getUntil() != null
michael@0 729 && candidate.after(getUntil()))) {
michael@0 730 return candidate;
michael@0 731 }
michael@0 732 }
michael@0 733 }
michael@0 734 } else {
michael@0 735 noCandidateIncrementCount++;
michael@0 736 if ((maxIncrementCount > 0) && (noCandidateIncrementCount > maxIncrementCount)) {
michael@0 737 break;
michael@0 738 }
michael@0 739 }
michael@0 740 increment(cal);
michael@0 741 }
michael@0 742 return null;
michael@0 743 }
michael@0 744
michael@0 745 /**
michael@0 746 * Increments the specified calendar according to the frequency and interval specified in this recurrence rule.
michael@0 747 * @param cal a java.util.Calendar to increment
michael@0 748 */
michael@0 749 private void increment(final Calendar cal) {
michael@0 750 // initialise interval..
michael@0 751 final int calInterval = (getInterval() >= 1) ? getInterval() : 1;
michael@0 752 cal.add(calIncField, calInterval);
michael@0 753 }
michael@0 754
michael@0 755 /**
michael@0 756 * Returns a list of possible dates generated from the applicable BY* rules, using the specified date as a seed.
michael@0 757 * @param date the seed date
michael@0 758 * @param value the type of date list to return
michael@0 759 * @return a DateList
michael@0 760 */
michael@0 761 private DateList getCandidates(final Date date, final Value value) {
michael@0 762 DateList dates = new DateList(value);
michael@0 763 if (date instanceof DateTime) {
michael@0 764 if (((DateTime) date).isUtc()) {
michael@0 765 dates.setUtc(true);
michael@0 766 }
michael@0 767 else {
michael@0 768 dates.setTimeZone(((DateTime) date).getTimeZone());
michael@0 769 }
michael@0 770 }
michael@0 771 dates.add(date);
michael@0 772 dates = getMonthVariants(dates);
michael@0 773 // debugging..
michael@0 774 if (log.isDebugEnabled()) {
michael@0 775 log.debug("Dates after BYMONTH processing: " + dates);
michael@0 776 }
michael@0 777 dates = getWeekNoVariants(dates);
michael@0 778 // debugging..
michael@0 779 if (log.isDebugEnabled()) {
michael@0 780 log.debug("Dates after BYWEEKNO processing: " + dates);
michael@0 781 }
michael@0 782 dates = getYearDayVariants(dates);
michael@0 783 // debugging..
michael@0 784 if (log.isDebugEnabled()) {
michael@0 785 log.debug("Dates after BYYEARDAY processing: " + dates);
michael@0 786 }
michael@0 787 dates = getMonthDayVariants(dates);
michael@0 788 // debugging..
michael@0 789 if (log.isDebugEnabled()) {
michael@0 790 log.debug("Dates after BYMONTHDAY processing: " + dates);
michael@0 791 }
michael@0 792 dates = getDayVariants(dates);
michael@0 793 // debugging..
michael@0 794 if (log.isDebugEnabled()) {
michael@0 795 log.debug("Dates after BYDAY processing: " + dates);
michael@0 796 }
michael@0 797 dates = getHourVariants(dates);
michael@0 798 // debugging..
michael@0 799 if (log.isDebugEnabled()) {
michael@0 800 log.debug("Dates after BYHOUR processing: " + dates);
michael@0 801 }
michael@0 802 dates = getMinuteVariants(dates);
michael@0 803 // debugging..
michael@0 804 if (log.isDebugEnabled()) {
michael@0 805 log.debug("Dates after BYMINUTE processing: " + dates);
michael@0 806 }
michael@0 807 dates = getSecondVariants(dates);
michael@0 808 // debugging..
michael@0 809 if (log.isDebugEnabled()) {
michael@0 810 log.debug("Dates after BYSECOND processing: " + dates);
michael@0 811 }
michael@0 812 dates = applySetPosRules(dates);
michael@0 813 // debugging..
michael@0 814 if (log.isDebugEnabled()) {
michael@0 815 log.debug("Dates after SETPOS processing: " + dates);
michael@0 816 }
michael@0 817 return dates;
michael@0 818 }
michael@0 819
michael@0 820 /**
michael@0 821 * Applies BYSETPOS rules to <code>dates</code>. Valid positions are from 1 to the size of the date list. Invalid
michael@0 822 * positions are ignored.
michael@0 823 * @param dates
michael@0 824 */
michael@0 825 private DateList applySetPosRules(final DateList dates) {
michael@0 826 // return if no SETPOS rules specified..
michael@0 827 if (getSetPosList().isEmpty()) {
michael@0 828 return dates;
michael@0 829 }
michael@0 830 // sort the list before processing..
michael@0 831 Collections.sort(dates);
michael@0 832 final DateList setPosDates = getDateListInstance(dates);
michael@0 833 final int size = dates.size();
michael@0 834 for (final Iterator i = getSetPosList().iterator(); i.hasNext();) {
michael@0 835 final Integer setPos = (Integer) i.next();
michael@0 836 final int pos = setPos.intValue();
michael@0 837 if (pos > 0 && pos <= size) {
michael@0 838 setPosDates.add(dates.get(pos - 1));
michael@0 839 }
michael@0 840 else if (pos < 0 && pos >= -size) {
michael@0 841 setPosDates.add(dates.get(size + pos));
michael@0 842 }
michael@0 843 }
michael@0 844 return setPosDates;
michael@0 845 }
michael@0 846
michael@0 847 /**
michael@0 848 * Applies BYMONTH rules specified in this Recur instance to the specified date list. If no BYMONTH rules are
michael@0 849 * specified the date list is returned unmodified.
michael@0 850 * @param dates
michael@0 851 * @return
michael@0 852 */
michael@0 853 private DateList getMonthVariants(final DateList dates) {
michael@0 854 if (getMonthList().isEmpty()) {
michael@0 855 return dates;
michael@0 856 }
michael@0 857 final DateList monthlyDates = getDateListInstance(dates);
michael@0 858 for (final Iterator i = dates.iterator(); i.hasNext();) {
michael@0 859 final Date date = (Date) i.next();
michael@0 860 final Calendar cal = Dates.getCalendarInstance(date);
michael@0 861 cal.setTime(date);
michael@0 862 for (final Iterator j = getMonthList().iterator(); j.hasNext();) {
michael@0 863 final Integer month = (Integer) j.next();
michael@0 864 // Java months are zero-based..
michael@0 865 // cal.set(Calendar.MONTH, month.intValue() - 1);
michael@0 866 cal.roll(Calendar.MONTH, (month.intValue() - 1) - cal.get(Calendar.MONTH));
michael@0 867 monthlyDates.add(Dates.getInstance(cal.getTime(), monthlyDates.getType()));
michael@0 868 }
michael@0 869 }
michael@0 870 return monthlyDates;
michael@0 871 }
michael@0 872
michael@0 873 /**
michael@0 874 * Applies BYWEEKNO rules specified in this Recur instance to the specified date list. If no BYWEEKNO rules are
michael@0 875 * specified the date list is returned unmodified.
michael@0 876 * @param dates
michael@0 877 * @return
michael@0 878 */
michael@0 879 private DateList getWeekNoVariants(final DateList dates) {
michael@0 880 if (getWeekNoList().isEmpty()) {
michael@0 881 return dates;
michael@0 882 }
michael@0 883 final DateList weekNoDates = getDateListInstance(dates);
michael@0 884 for (final Iterator i = dates.iterator(); i.hasNext();) {
michael@0 885 final Date date = (Date) i.next();
michael@0 886 final Calendar cal = Dates.getCalendarInstance(date);
michael@0 887 cal.setTime(date);
michael@0 888 for (final Iterator j = getWeekNoList().iterator(); j.hasNext();) {
michael@0 889 final Integer weekNo = (Integer) j.next();
michael@0 890 cal.set(Calendar.WEEK_OF_YEAR, Dates.getAbsWeekNo(cal.getTime(), weekNo.intValue()));
michael@0 891 weekNoDates.add(Dates.getInstance(cal.getTime(), weekNoDates.getType()));
michael@0 892 }
michael@0 893 }
michael@0 894 return weekNoDates;
michael@0 895 }
michael@0 896
michael@0 897 /**
michael@0 898 * Applies BYYEARDAY rules specified in this Recur instance to the specified date list. If no BYYEARDAY rules are
michael@0 899 * specified the date list is returned unmodified.
michael@0 900 * @param dates
michael@0 901 * @return
michael@0 902 */
michael@0 903 private DateList getYearDayVariants(final DateList dates) {
michael@0 904 if (getYearDayList().isEmpty()) {
michael@0 905 return dates;
michael@0 906 }
michael@0 907 final DateList yearDayDates = getDateListInstance(dates);
michael@0 908 for (final Iterator i = dates.iterator(); i.hasNext();) {
michael@0 909 final Date date = (Date) i.next();
michael@0 910 final Calendar cal = Dates.getCalendarInstance(date);
michael@0 911 cal.setTime(date);
michael@0 912 for (final Iterator j = getYearDayList().iterator(); j.hasNext();) {
michael@0 913 final Integer yearDay = (Integer) j.next();
michael@0 914 cal.set(Calendar.DAY_OF_YEAR, Dates.getAbsYearDay(cal.getTime(), yearDay.intValue()));
michael@0 915 yearDayDates.add(Dates.getInstance(cal.getTime(), yearDayDates.getType()));
michael@0 916 }
michael@0 917 }
michael@0 918 return yearDayDates;
michael@0 919 }
michael@0 920
michael@0 921 /**
michael@0 922 * Applies BYMONTHDAY rules specified in this Recur instance to the specified date list. If no BYMONTHDAY rules are
michael@0 923 * specified the date list is returned unmodified.
michael@0 924 * @param dates
michael@0 925 * @return
michael@0 926 */
michael@0 927 private DateList getMonthDayVariants(final DateList dates) {
michael@0 928 if (getMonthDayList().isEmpty()) {
michael@0 929 return dates;
michael@0 930 }
michael@0 931 final DateList monthDayDates = getDateListInstance(dates);
michael@0 932 for (final Iterator i = dates.iterator(); i.hasNext();) {
michael@0 933 final Date date = (Date) i.next();
michael@0 934 final Calendar cal = Dates.getCalendarInstance(date);
michael@0 935 cal.setLenient(false);
michael@0 936 cal.setTime(date);
michael@0 937 for (final Iterator j = getMonthDayList().iterator(); j.hasNext();) {
michael@0 938 final Integer monthDay = (Integer) j.next();
michael@0 939 try {
michael@0 940 cal.set(Calendar.DAY_OF_MONTH, Dates.getAbsMonthDay(cal.getTime(), monthDay.intValue()));
michael@0 941 monthDayDates.add(Dates.getInstance(cal.getTime(), monthDayDates.getType()));
michael@0 942 }
michael@0 943 catch (IllegalArgumentException iae) {
michael@0 944 if (log.isTraceEnabled()) {
michael@0 945 log.trace("Invalid day of month: " + Dates.getAbsMonthDay(cal
michael@0 946 .getTime(), monthDay.intValue()));
michael@0 947 }
michael@0 948 }
michael@0 949 }
michael@0 950 }
michael@0 951 return monthDayDates;
michael@0 952 }
michael@0 953
michael@0 954 /**
michael@0 955 * Applies BYDAY rules specified in this Recur instance to the specified date list. If no BYDAY rules are specified
michael@0 956 * the date list is returned unmodified.
michael@0 957 * @param dates
michael@0 958 * @return
michael@0 959 */
michael@0 960 private DateList getDayVariants(final DateList dates) {
michael@0 961 if (getDayList().isEmpty()) {
michael@0 962 return dates;
michael@0 963 }
michael@0 964 final DateList weekDayDates = getDateListInstance(dates);
michael@0 965 for (final Iterator i = dates.iterator(); i.hasNext();) {
michael@0 966 final Date date = (Date) i.next();
michael@0 967 for (final Iterator j = getDayList().iterator(); j.hasNext();) {
michael@0 968 final WeekDay weekDay = (WeekDay) j.next();
michael@0 969 // if BYYEARDAY or BYMONTHDAY is specified filter existing
michael@0 970 // list..
michael@0 971 if (!getYearDayList().isEmpty() || !getMonthDayList().isEmpty()) {
michael@0 972 final Calendar cal = Dates.getCalendarInstance(date);
michael@0 973 cal.setTime(date);
michael@0 974 if (weekDay.equals(WeekDay.getWeekDay(cal))) {
michael@0 975 weekDayDates.add(date);
michael@0 976 }
michael@0 977 }
michael@0 978 else {
michael@0 979 weekDayDates.addAll(getAbsWeekDays(date, dates.getType(), weekDay));
michael@0 980 }
michael@0 981 }
michael@0 982 }
michael@0 983 return weekDayDates;
michael@0 984 }
michael@0 985
michael@0 986 /**
michael@0 987 * Returns a list of applicable dates corresponding to the specified week day in accordance with the frequency
michael@0 988 * specified by this recurrence rule.
michael@0 989 * @param date
michael@0 990 * @param weekDay
michael@0 991 * @return
michael@0 992 */
michael@0 993 private List getAbsWeekDays(final Date date, final Value type, final WeekDay weekDay) {
michael@0 994 final Calendar cal = Dates.getCalendarInstance(date);
michael@0 995 // default week start is Monday per RFC5545
michael@0 996 int calendarWeekStartDay = Calendar.MONDAY;
michael@0 997 if (weekStartDay != null) {
michael@0 998 calendarWeekStartDay = WeekDay.getCalendarDay(new WeekDay(weekStartDay));
michael@0 999 }
michael@0 1000 cal.setFirstDayOfWeek(calendarWeekStartDay);
michael@0 1001 cal.setTime(date);
michael@0 1002
michael@0 1003 final DateList days = new DateList(type);
michael@0 1004 if (date instanceof DateTime) {
michael@0 1005 if (((DateTime) date).isUtc()) {
michael@0 1006 days.setUtc(true);
michael@0 1007 }
michael@0 1008 else {
michael@0 1009 days.setTimeZone(((DateTime) date).getTimeZone());
michael@0 1010 }
michael@0 1011 }
michael@0 1012 final int calDay = WeekDay.getCalendarDay(weekDay);
michael@0 1013 if (calDay == -1) {
michael@0 1014 // a matching weekday cannot be identified..
michael@0 1015 return days;
michael@0 1016 }
michael@0 1017 if (DAILY.equals(getFrequency())) {
michael@0 1018 if (cal.get(Calendar.DAY_OF_WEEK) == calDay) {
michael@0 1019 days.add(Dates.getInstance(cal.getTime(), type));
michael@0 1020 }
michael@0 1021 }
michael@0 1022 else if (WEEKLY.equals(getFrequency()) || !getWeekNoList().isEmpty()) {
michael@0 1023 final int weekNo = cal.get(Calendar.WEEK_OF_YEAR);
michael@0 1024 // construct a list of possible week days..
michael@0 1025 cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
michael@0 1026 while (cal.get(Calendar.DAY_OF_WEEK) != calDay) {
michael@0 1027 cal.add(Calendar.DAY_OF_WEEK, 1);
michael@0 1028 }
michael@0 1029 // final int weekNo = cal.get(Calendar.WEEK_OF_YEAR);
michael@0 1030 if (cal.get(Calendar.WEEK_OF_YEAR) == weekNo) {
michael@0 1031 days.add(Dates.getInstance(cal.getTime(), type));
michael@0 1032 // cal.add(Calendar.DAY_OF_WEEK, Dates.DAYS_PER_WEEK);
michael@0 1033 }
michael@0 1034 }
michael@0 1035 else if (MONTHLY.equals(getFrequency()) || !getMonthList().isEmpty()) {
michael@0 1036 final int month = cal.get(Calendar.MONTH);
michael@0 1037 // construct a list of possible month days..
michael@0 1038 cal.set(Calendar.DAY_OF_MONTH, 1);
michael@0 1039 while (cal.get(Calendar.DAY_OF_WEEK) != calDay) {
michael@0 1040 cal.add(Calendar.DAY_OF_MONTH, 1);
michael@0 1041 }
michael@0 1042 while (cal.get(Calendar.MONTH) == month) {
michael@0 1043 days.add(Dates.getInstance(cal.getTime(), type));
michael@0 1044 cal.add(Calendar.DAY_OF_MONTH, Dates.DAYS_PER_WEEK);
michael@0 1045 }
michael@0 1046 }
michael@0 1047 else if (YEARLY.equals(getFrequency())) {
michael@0 1048 final int year = cal.get(Calendar.YEAR);
michael@0 1049 // construct a list of possible year days..
michael@0 1050 cal.set(Calendar.DAY_OF_YEAR, 1);
michael@0 1051 while (cal.get(Calendar.DAY_OF_WEEK) != calDay) {
michael@0 1052 cal.add(Calendar.DAY_OF_YEAR, 1);
michael@0 1053 }
michael@0 1054 while (cal.get(Calendar.YEAR) == year) {
michael@0 1055 days.add(Dates.getInstance(cal.getTime(), type));
michael@0 1056 cal.add(Calendar.DAY_OF_YEAR, Dates.DAYS_PER_WEEK);
michael@0 1057 }
michael@0 1058 }
michael@0 1059 return getOffsetDates(days, weekDay.getOffset());
michael@0 1060 }
michael@0 1061
michael@0 1062 /**
michael@0 1063 * Returns a single-element sublist containing the element of <code>list</code> at <code>offset</code>. Valid
michael@0 1064 * offsets are from 1 to the size of the list. If an invalid offset is supplied, all elements from <code>list</code>
michael@0 1065 * are added to <code>sublist</code>.
michael@0 1066 * @param list
michael@0 1067 * @param offset
michael@0 1068 * @param sublist
michael@0 1069 */
michael@0 1070 private List getOffsetDates(final DateList dates, final int offset) {
michael@0 1071 if (offset == 0) {
michael@0 1072 return dates;
michael@0 1073 }
michael@0 1074 final List offsetDates = getDateListInstance(dates);
michael@0 1075 final int size = dates.size();
michael@0 1076 if (offset < 0 && offset >= -size) {
michael@0 1077 offsetDates.add(dates.get(size + offset));
michael@0 1078 }
michael@0 1079 else if (offset > 0 && offset <= size) {
michael@0 1080 offsetDates.add(dates.get(offset - 1));
michael@0 1081 }
michael@0 1082 return offsetDates;
michael@0 1083 }
michael@0 1084
michael@0 1085 /**
michael@0 1086 * Applies BYHOUR rules specified in this Recur instance to the specified date list. If no BYHOUR rules are
michael@0 1087 * specified the date list is returned unmodified.
michael@0 1088 * @param dates
michael@0 1089 * @return
michael@0 1090 */
michael@0 1091 private DateList getHourVariants(final DateList dates) {
michael@0 1092 if (getHourList().isEmpty()) {
michael@0 1093 return dates;
michael@0 1094 }
michael@0 1095 final DateList hourlyDates = getDateListInstance(dates);
michael@0 1096 for (final Iterator i = dates.iterator(); i.hasNext();) {
michael@0 1097 final Date date = (Date) i.next();
michael@0 1098 final Calendar cal = Dates.getCalendarInstance(date);
michael@0 1099 cal.setTime(date);
michael@0 1100 for (final Iterator j = getHourList().iterator(); j.hasNext();) {
michael@0 1101 final Integer hour = (Integer) j.next();
michael@0 1102 cal.set(Calendar.HOUR_OF_DAY, hour.intValue());
michael@0 1103 hourlyDates.add(Dates.getInstance(cal.getTime(), hourlyDates.getType()));
michael@0 1104 }
michael@0 1105 }
michael@0 1106 return hourlyDates;
michael@0 1107 }
michael@0 1108
michael@0 1109 /**
michael@0 1110 * Applies BYMINUTE rules specified in this Recur instance to the specified date list. If no BYMINUTE rules are
michael@0 1111 * specified the date list is returned unmodified.
michael@0 1112 * @param dates
michael@0 1113 * @return
michael@0 1114 */
michael@0 1115 private DateList getMinuteVariants(final DateList dates) {
michael@0 1116 if (getMinuteList().isEmpty()) {
michael@0 1117 return dates;
michael@0 1118 }
michael@0 1119 final DateList minutelyDates = getDateListInstance(dates);
michael@0 1120 for (final Iterator i = dates.iterator(); i.hasNext();) {
michael@0 1121 final Date date = (Date) i.next();
michael@0 1122 final Calendar cal = Dates.getCalendarInstance(date);
michael@0 1123 cal.setTime(date);
michael@0 1124 for (final Iterator j = getMinuteList().iterator(); j.hasNext();) {
michael@0 1125 final Integer minute = (Integer) j.next();
michael@0 1126 cal.set(Calendar.MINUTE, minute.intValue());
michael@0 1127 minutelyDates.add(Dates.getInstance(cal.getTime(), minutelyDates.getType()));
michael@0 1128 }
michael@0 1129 }
michael@0 1130 return minutelyDates;
michael@0 1131 }
michael@0 1132
michael@0 1133 /**
michael@0 1134 * Applies BYSECOND rules specified in this Recur instance to the specified date list. If no BYSECOND rules are
michael@0 1135 * specified the date list is returned unmodified.
michael@0 1136 * @param dates
michael@0 1137 * @return
michael@0 1138 */
michael@0 1139 private DateList getSecondVariants(final DateList dates) {
michael@0 1140 if (getSecondList().isEmpty()) {
michael@0 1141 return dates;
michael@0 1142 }
michael@0 1143 final DateList secondlyDates = getDateListInstance(dates);
michael@0 1144 for (final Iterator i = dates.iterator(); i.hasNext();) {
michael@0 1145 final Date date = (Date) i.next();
michael@0 1146 final Calendar cal = Dates.getCalendarInstance(date);
michael@0 1147 cal.setTime(date);
michael@0 1148 for (final Iterator j = getSecondList().iterator(); j.hasNext();) {
michael@0 1149 final Integer second = (Integer) j.next();
michael@0 1150 cal.set(Calendar.SECOND, second.intValue());
michael@0 1151 secondlyDates.add(Dates.getInstance(cal.getTime(), secondlyDates.getType()));
michael@0 1152 }
michael@0 1153 }
michael@0 1154 return secondlyDates;
michael@0 1155 }
michael@0 1156
michael@0 1157 private void validateFrequency() {
michael@0 1158 if (frequency == null) {
michael@0 1159 throw new IllegalArgumentException(
michael@0 1160 "A recurrence rule MUST contain a FREQ rule part.");
michael@0 1161 }
michael@0 1162 if (SECONDLY.equals(getFrequency())) {
michael@0 1163 calIncField = Calendar.SECOND;
michael@0 1164 }
michael@0 1165 else if (MINUTELY.equals(getFrequency())) {
michael@0 1166 calIncField = Calendar.MINUTE;
michael@0 1167 }
michael@0 1168 else if (HOURLY.equals(getFrequency())) {
michael@0 1169 calIncField = Calendar.HOUR_OF_DAY;
michael@0 1170 }
michael@0 1171 else if (DAILY.equals(getFrequency())) {
michael@0 1172 calIncField = Calendar.DAY_OF_YEAR;
michael@0 1173 }
michael@0 1174 else if (WEEKLY.equals(getFrequency())) {
michael@0 1175 calIncField = Calendar.WEEK_OF_YEAR;
michael@0 1176 }
michael@0 1177 else if (MONTHLY.equals(getFrequency())) {
michael@0 1178 calIncField = Calendar.MONTH;
michael@0 1179 }
michael@0 1180 else if (YEARLY.equals(getFrequency())) {
michael@0 1181 calIncField = Calendar.YEAR;
michael@0 1182 }
michael@0 1183 else {
michael@0 1184 throw new IllegalArgumentException("Invalid FREQ rule part '"
michael@0 1185 + frequency + "' in recurrence rule");
michael@0 1186 }
michael@0 1187 }
michael@0 1188
michael@0 1189 /**
michael@0 1190 * @param count The count to set.
michael@0 1191 */
michael@0 1192 public final void setCount(final int count) {
michael@0 1193 this.count = count;
michael@0 1194 this.until = null;
michael@0 1195 }
michael@0 1196
michael@0 1197 /**
michael@0 1198 * @param frequency The frequency to set.
michael@0 1199 */
michael@0 1200 public final void setFrequency(final String frequency) {
michael@0 1201 this.frequency = frequency;
michael@0 1202 validateFrequency();
michael@0 1203 }
michael@0 1204
michael@0 1205 /**
michael@0 1206 * @param interval The interval to set.
michael@0 1207 */
michael@0 1208 public final void setInterval(final int interval) {
michael@0 1209 this.interval = interval;
michael@0 1210 }
michael@0 1211
michael@0 1212 /**
michael@0 1213 * @param until The until to set.
michael@0 1214 */
michael@0 1215 public final void setUntil(final Date until) {
michael@0 1216 this.until = until;
michael@0 1217 this.count = -1;
michael@0 1218 }
michael@0 1219
michael@0 1220 /**
michael@0 1221 * @param stream
michael@0 1222 * @throws IOException
michael@0 1223 * @throws ClassNotFoundException
michael@0 1224 */
michael@0 1225 private void readObject(final java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException {
michael@0 1226 stream.defaultReadObject();
michael@0 1227 log = LogFactory.getLog(Recur.class);
michael@0 1228 }
michael@0 1229
michael@0 1230 /**
michael@0 1231 * Instantiate a new datelist with the same type, timezone and utc settings
michael@0 1232 * as the origList.
michael@0 1233 * @param origList
michael@0 1234 * @return a new empty list.
michael@0 1235 */
michael@0 1236 private static final DateList getDateListInstance(final DateList origList) {
michael@0 1237 final DateList list = new DateList(origList.getType());
michael@0 1238 if (origList.isUtc()) {
michael@0 1239 list.setUtc(true);
michael@0 1240 } else {
michael@0 1241 list.setTimeZone(origList.getTimeZone());
michael@0 1242 }
michael@0 1243 return list;
michael@0 1244 }
michael@0 1245
michael@0 1246 }

mercurial