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

Tue, 10 Feb 2015 19:38:00 +0100

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

Upgrade embedded ical4j from ancient whatever to upstream version 1.0.6.

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

mercurial