diff -r 5ae3e5665a0b -r cc93757aeca3 src/net/fortuna/ical4j/model/Recur.java --- a/src/net/fortuna/ical4j/model/Recur.java Thu Feb 12 18:02:00 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1266 +0,0 @@ -/** - * Copyright (c) 2012, Ben Fortuna - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * o Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * o Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * o Neither the name of Ben Fortuna nor the names of any other contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.fortuna.ical4j.model; - -import java.io.IOException; -import java.io.Serializable; -import java.text.ParseException; -import java.util.Calendar; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.StringTokenizer; - -import net.fortuna.ical4j.model.parameter.Value; -import net.fortuna.ical4j.util.CompatibilityHints; -import net.fortuna.ical4j.util.Configurator; -import net.fortuna.ical4j.util.Dates; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * $Id$ [18-Apr-2004] - * - * Defines a recurrence. - * @version 2.0 - * @author Ben Fortuna - */ -public class Recur implements Serializable { - - private static final long serialVersionUID = -7333226591784095142L; - - private static final String FREQ = "FREQ"; - - private static final String UNTIL = "UNTIL"; - - private static final String COUNT = "COUNT"; - - private static final String INTERVAL = "INTERVAL"; - - private static final String BYSECOND = "BYSECOND"; - - private static final String BYMINUTE = "BYMINUTE"; - - private static final String BYHOUR = "BYHOUR"; - - private static final String BYDAY = "BYDAY"; - - private static final String BYMONTHDAY = "BYMONTHDAY"; - - private static final String BYYEARDAY = "BYYEARDAY"; - - private static final String BYWEEKNO = "BYWEEKNO"; - - private static final String BYMONTH = "BYMONTH"; - - private static final String BYSETPOS = "BYSETPOS"; - - private static final String WKST = "WKST"; - - /** - * Second frequency resolution. - */ - public static final String SECONDLY = "SECONDLY"; - - /** - * Minute frequency resolution. - */ - public static final String MINUTELY = "MINUTELY"; - - /** - * Hour frequency resolution. - */ - public static final String HOURLY = "HOURLY"; - - /** - * Day frequency resolution. - */ - public static final String DAILY = "DAILY"; - - /** - * Week frequency resolution. - */ - public static final String WEEKLY = "WEEKLY"; - - /** - * Month frequency resolution. - */ - public static final String MONTHLY = "MONTHLY"; - - /** - * Year frequency resolution. - */ - public static final String YEARLY = "YEARLY"; - - /** - * When calculating dates matching this recur ({@code getDates()} or {@code getNextDate}), - * this property defines the maximum number of attempt to find a matching date by - * incrementing the seed. - *
The default value is 1000. A value of -1 corresponds to no maximum.
- */ - public static final String KEY_MAX_INCREMENT_COUNT = "net.fortuna.ical4j.recur.maxincrementcount"; - - private static int maxIncrementCount; - static { - final String value = Configurator.getProperty(KEY_MAX_INCREMENT_COUNT); - if (value != null && value.length() > 0) { - maxIncrementCount = Integer.parseInt(value); - } else { - maxIncrementCount = 1000; - } - } - - private transient Log log = LogFactory.getLog(Recur.class); - - private String frequency; - - private Date until; - - private int count = -1; - - private int interval = -1; - - private NumberList secondList; - - private NumberList minuteList; - - private NumberList hourList; - - private WeekDayList dayList; - - private NumberList monthDayList; - - private NumberList yearDayList; - - private NumberList weekNoList; - - private NumberList monthList; - - private NumberList setPosList; - - private String weekStartDay; - - private int calendarWeekStartDay; - - private Map experimentalValues = new HashMap(); - - // Calendar field we increment based on frequency. - private int calIncField; - - /** - * Default constructor. - */ - public Recur() { - // default week start is Monday per RFC5545 - calendarWeekStartDay = Calendar.MONDAY; - } - - /** - * Constructs a new instance from the specified string value. - * @param aValue a string representation of a recurrence. - * @throws ParseException thrown when the specified string contains an invalid representation of an UNTIL date value - */ - public Recur(final String aValue) throws ParseException { - // default week start is Monday per RFC5545 - calendarWeekStartDay = Calendar.MONDAY; - final StringTokenizer t = new StringTokenizer(aValue, ";="); - while (t.hasMoreTokens()) { - final String token = t.nextToken(); - if (FREQ.equals(token)) { - frequency = nextToken(t, token); - } - else if (UNTIL.equals(token)) { - final String untilString = nextToken(t, token); - if (untilString != null && untilString.indexOf("T") >= 0) { - until = new DateTime(untilString); - // UNTIL must be specified in UTC time.. - ((DateTime) until).setUtc(true); - } - else { - until = new Date(untilString); - } - } - else if (COUNT.equals(token)) { - count = Integer.parseInt(nextToken(t, token)); - } - else if (INTERVAL.equals(token)) { - interval = Integer.parseInt(nextToken(t, token)); - } - else if (BYSECOND.equals(token)) { - secondList = new NumberList(nextToken(t, token), 0, 59, false); - } - else if (BYMINUTE.equals(token)) { - minuteList = new NumberList(nextToken(t, token), 0, 59, false); - } - else if (BYHOUR.equals(token)) { - hourList = new NumberList(nextToken(t, token), 0, 23, false); - } - else if (BYDAY.equals(token)) { - dayList = new WeekDayList(nextToken(t, token)); - } - else if (BYMONTHDAY.equals(token)) { - monthDayList = new NumberList(nextToken(t, token), 1, 31, true); - } - else if (BYYEARDAY.equals(token)) { - yearDayList = new NumberList(nextToken(t, token), 1, 366, true); - } - else if (BYWEEKNO.equals(token)) { - weekNoList = new NumberList(nextToken(t, token), 1, 53, true); - } - else if (BYMONTH.equals(token)) { - monthList = new NumberList(nextToken(t, token), 1, 12, false); - } - else if (BYSETPOS.equals(token)) { - setPosList = new NumberList(nextToken(t, token), 1, 366, true); - } - else if (WKST.equals(token)) { - weekStartDay = nextToken(t, token); - calendarWeekStartDay = WeekDay.getCalendarDay(new WeekDay(weekStartDay)); - } - else { - if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING)) { - // assume experimental value.. - experimentalValues.put(token, nextToken(t, token)); - } - else { - throw new IllegalArgumentException("Invalid recurrence rule part: " + - token + "=" + nextToken(t, token)); - } - } - } - validateFrequency(); - } - - private String nextToken(StringTokenizer t, String lastToken) { - try { - return t.nextToken(); - } - catch (NoSuchElementException e) { - throw new IllegalArgumentException("Missing expected token, last token: " + lastToken); - } - } - - /** - * @param frequency a recurrence frequency string - * @param until maximum recurrence date - */ - public Recur(final String frequency, final Date until) { - // default week start is Monday per RFC5545 - calendarWeekStartDay = Calendar.MONDAY; - this.frequency = frequency; - this.until = until; - validateFrequency(); - } - - /** - * @param frequency a recurrence frequency string - * @param count maximum recurrence count - */ - public Recur(final String frequency, final int count) { - // default week start is Monday per RFC5545 - calendarWeekStartDay = Calendar.MONDAY; - this.frequency = frequency; - this.count = count; - validateFrequency(); - } - - /** - * @return Returns the dayList. - */ - public final WeekDayList getDayList() { - if (dayList == null) { - dayList = new WeekDayList(); - } - return dayList; - } - - /** - * @return Returns the hourList. - */ - public final NumberList getHourList() { - if (hourList == null) { - hourList = new NumberList(0, 23, false); - } - return hourList; - } - - /** - * @return Returns the minuteList. - */ - public final NumberList getMinuteList() { - if (minuteList == null) { - minuteList = new NumberList(0, 59, false); - } - return minuteList; - } - - /** - * @return Returns the monthDayList. - */ - public final NumberList getMonthDayList() { - if (monthDayList == null) { - monthDayList = new NumberList(1, 31, true); - } - return monthDayList; - } - - /** - * @return Returns the monthList. - */ - public final NumberList getMonthList() { - if (monthList == null) { - monthList = new NumberList(1, 12, false); - } - return monthList; - } - - /** - * @return Returns the secondList. - */ - public final NumberList getSecondList() { - if (secondList == null) { - secondList = new NumberList(0, 59, false); - } - return secondList; - } - - /** - * @return Returns the setPosList. - */ - public final NumberList getSetPosList() { - if (setPosList == null) { - setPosList = new NumberList(1, 366, true); - } - return setPosList; - } - - /** - * @return Returns the weekNoList. - */ - public final NumberList getWeekNoList() { - if (weekNoList == null) { - weekNoList = new NumberList(1, 53, true); - } - return weekNoList; - } - - /** - * @return Returns the yearDayList. - */ - public final NumberList getYearDayList() { - if (yearDayList == null) { - yearDayList = new NumberList(1, 366, true); - } - return yearDayList; - } - - /** - * @return Returns the count or -1 if the rule does not have a count. - */ - public final int getCount() { - return count; - } - - /** - * @return Returns the experimentalValues. - */ - public final Map getExperimentalValues() { - return experimentalValues; - } - - /** - * @return Returns the frequency. - */ - public final String getFrequency() { - return frequency; - } - - /** - * @return Returns the interval or -1 if the rule does not have an interval defined. - */ - public final int getInterval() { - return interval; - } - - /** - * @return Returns the until or null if there is none. - */ - public final Date getUntil() { - return until; - } - - /** - * @return Returns the weekStartDay or null if there is none. - */ - public final String getWeekStartDay() { - return weekStartDay; - } - - /** - * @param weekStartDay The weekStartDay to set. - */ - public final void setWeekStartDay(final String weekStartDay) { - this.weekStartDay = weekStartDay; - if (weekStartDay != null) { - calendarWeekStartDay = WeekDay.getCalendarDay(new WeekDay(weekStartDay)); - } - } - - /** - * {@inheritDoc} - */ - public final String toString() { - final StringBuffer b = new StringBuffer(); - b.append(FREQ); - b.append('='); - b.append(frequency); - if (weekStartDay != null) { - b.append(';'); - b.append(WKST); - b.append('='); - b.append(weekStartDay); - } - if (until != null) { - b.append(';'); - b.append(UNTIL); - b.append('='); - // Note: date-time representations should always be in UTC time. - b.append(until); - } - if (count >= 1) { - b.append(';'); - b.append(COUNT); - b.append('='); - b.append(count); - } - if (interval >= 1) { - b.append(';'); - b.append(INTERVAL); - b.append('='); - b.append(interval); - } - if (!getMonthList().isEmpty()) { - b.append(';'); - b.append(BYMONTH); - b.append('='); - b.append(monthList); - } - if (!getWeekNoList().isEmpty()) { - b.append(';'); - b.append(BYWEEKNO); - b.append('='); - b.append(weekNoList); - } - if (!getYearDayList().isEmpty()) { - b.append(';'); - b.append(BYYEARDAY); - b.append('='); - b.append(yearDayList); - } - if (!getMonthDayList().isEmpty()) { - b.append(';'); - b.append(BYMONTHDAY); - b.append('='); - b.append(monthDayList); - } - if (!getDayList().isEmpty()) { - b.append(';'); - b.append(BYDAY); - b.append('='); - b.append(dayList); - } - if (!getHourList().isEmpty()) { - b.append(';'); - b.append(BYHOUR); - b.append('='); - b.append(hourList); - } - if (!getMinuteList().isEmpty()) { - b.append(';'); - b.append(BYMINUTE); - b.append('='); - b.append(minuteList); - } - if (!getSecondList().isEmpty()) { - b.append(';'); - b.append(BYSECOND); - b.append('='); - b.append(secondList); - } - if (!getSetPosList().isEmpty()) { - b.append(';'); - b.append(BYSETPOS); - b.append('='); - b.append(setPosList); - } - return b.toString(); - } - - /** - * Returns a list of start dates in the specified period represented by this recur. Any date fields not specified by - * this recur are retained from the period start, and as such you should ensure the period start is initialised - * correctly. - * @param periodStart the start of the period - * @param periodEnd the end of the period - * @param value the type of dates to generate (i.e. date/date-time) - * @return a list of dates - */ - public final DateList getDates(final Date periodStart, - final Date periodEnd, final Value value) { - return getDates(periodStart, periodStart, periodEnd, value, -1); - } - - /** - * Convenience method for retrieving recurrences in a specified period. - * @param seed a seed date for generating recurrence instances - * @param period the period of returned recurrence dates - * @param value type of dates to generate - * @return a list of dates - */ - public final DateList getDates(final Date seed, final Period period, - final Value value) { - return getDates(seed, period.getStart(), period.getEnd(), value, -1); - } - - /** - * Returns a list of start dates in the specified period represented by this recur. This method includes a base date - * argument, which indicates the start of the fist occurrence of this recurrence. The base date is used to inject - * default values to return a set of dates in the correct format. For example, if the search start date (start) is - * Wed, Mar 23, 12:19PM, but the recurrence is Mon - Fri, 9:00AM - 5:00PM, the start dates returned should all be at - * 9:00AM, and not 12:19PM. - * @return a list of dates represented by this recur instance - * @param seed the start date of this Recurrence's first instance - * @param periodStart the start of the period - * @param periodEnd the end of the period - * @param value the type of dates to generate (i.e. date/date-time) - */ - public final DateList getDates(final Date seed, final Date periodStart, - final Date periodEnd, final Value value) { - return getDates(seed, periodStart, periodEnd, value, -1); - } - - /** - * Returns a list of start dates in the specified period represented by this recur. This method includes a base date - * argument, which indicates the start of the fist occurrence of this recurrence. The base date is used to inject - * default values to return a set of dates in the correct format. For example, if the search start date (start) is - * Wed, Mar 23, 12:19PM, but the recurrence is Mon - Fri, 9:00AM - 5:00PM, the start dates returned should all be at - * 9:00AM, and not 12:19PM. - * @return a list of dates represented by this recur instance - * @param seed the start date of this Recurrence's first instance - * @param periodStart the start of the period - * @param periodEnd the end of the period - * @param value the type of dates to generate (i.e. date/date-time) - * @param maxCount limits the number of instances returned. Up to one years - * worth extra may be returned. Less than 0 means no limit - */ - public final DateList getDates(final Date seed, final Date periodStart, - final Date periodEnd, final Value value, - final int maxCount) { - - final DateList dates = new DateList(value); - if (seed instanceof DateTime) { - if (((DateTime) seed).isUtc()) { - dates.setUtc(true); - } - else { - dates.setTimeZone(((DateTime) seed).getTimeZone()); - } - } - final Calendar cal = getCalendarInstance(seed, true); - - // optimize the start time for selecting candidates - // (only applicable where a COUNT is not specified) - if (getCount() < 1) { - final Calendar seededCal = (Calendar) cal.clone(); - while (seededCal.getTime().before(periodStart)) { - cal.setTime(seededCal.getTime()); - increment(seededCal); - } - } - - int invalidCandidateCount = 0; - int noCandidateIncrementCount = 0; - Date candidate = null; - while ((maxCount < 0) || (dates.size() < maxCount)) { - final Date candidateSeed = Dates.getInstance(cal.getTime(), value); - - if (getUntil() != null && candidate != null - && candidate.after(getUntil())) { - - break; - } - if (periodEnd != null && candidate != null - && candidate.after(periodEnd)) { - - break; - } - if (getCount() >= 1 - && (dates.size() + invalidCandidateCount) >= getCount()) { - - break; - } - -// if (Value.DATE_TIME.equals(value)) { - if (candidateSeed instanceof DateTime) { - if (dates.isUtc()) { - ((DateTime) candidateSeed).setUtc(true); - } - else { - ((DateTime) candidateSeed).setTimeZone(dates.getTimeZone()); - } - } - - final DateList candidates = getCandidates(candidateSeed, value); - if (!candidates.isEmpty()) { - noCandidateIncrementCount = 0; - // sort candidates for identifying when UNTIL date is exceeded.. - Collections.sort(candidates); - for (final Iterator i = candidates.iterator(); i.hasNext();) { - candidate = (Date) i.next(); - // don't count candidates that occur before the seed date.. - if (!candidate.before(seed)) { - // candidates exclusive of periodEnd.. - if (candidate.before(periodStart) - || !candidate.before(periodEnd)) { - invalidCandidateCount++; - } else if (getCount() >= 1 - && (dates.size() + invalidCandidateCount) >= getCount()) { - break; - } else if (!(getUntil() != null - && candidate.after(getUntil()))) { - dates.add(candidate); - } - } - } - } else { - noCandidateIncrementCount++; - if ((maxIncrementCount > 0) && (noCandidateIncrementCount > maxIncrementCount)) { - break; - } - } - increment(cal); - } - // sort final list.. - Collections.sort(dates); - return dates; - } - - /** - * Returns the the next date of this recurrence given a seed date - * and start date. The seed date indicates the start of the fist - * occurrence of this recurrence. The start date is the - * starting date to search for the next recurrence. Return null - * if there is no occurrence date after start date. - * @return the next date in the recurrence series after startDate - * @param seed the start date of this Recurrence's first instance - * @param startDate the date to start the search - */ - public final Date getNextDate(final Date seed, final Date startDate) { - - final Calendar cal = getCalendarInstance(seed, true); - - // optimize the start time for selecting candidates - // (only applicable where a COUNT is not specified) - if (getCount() < 1) { - final Calendar seededCal = (Calendar) cal.clone(); - while (seededCal.getTime().before(startDate)) { - cal.setTime(seededCal.getTime()); - increment(seededCal); - } - } - - int invalidCandidateCount = 0; - int noCandidateIncrementCount = 0; - Date candidate = null; - final Value value = seed instanceof DateTime ? Value.DATE_TIME : Value.DATE; - - while (true) { - final Date candidateSeed = Dates.getInstance(cal.getTime(), value); - - if (getUntil() != null && candidate != null && candidate.after(getUntil())) { - break; - } - - if (getCount() > 0 && invalidCandidateCount >= getCount()) { - break; - } - - if (Value.DATE_TIME.equals(value)) { - if (((DateTime) seed).isUtc()) { - ((DateTime) candidateSeed).setUtc(true); - } - else { - ((DateTime) candidateSeed).setTimeZone(((DateTime) seed).getTimeZone()); - } - } - - final DateList candidates = getCandidates(candidateSeed, value); - if (!candidates.isEmpty()) { - noCandidateIncrementCount = 0; - // sort candidates for identifying when UNTIL date is exceeded.. - Collections.sort(candidates); - - for (final Iterator i = candidates.iterator(); i.hasNext();) { - candidate = (Date) i.next(); - // don't count candidates that occur before the seed date.. - if (!candidate.before(seed)) { - // Candidate must be after startDate because - // we want the NEXT occurrence - if (!candidate.after(startDate)) { - invalidCandidateCount++; - } else if (getCount() > 0 - && invalidCandidateCount >= getCount()) { - break; - } else if (!(getUntil() != null - && candidate.after(getUntil()))) { - return candidate; - } - } - } - } else { - noCandidateIncrementCount++; - if ((maxIncrementCount > 0) && (noCandidateIncrementCount > maxIncrementCount)) { - break; - } - } - increment(cal); - } - return null; - } - - /** - * Increments the specified calendar according to the frequency and interval specified in this recurrence rule. - * @param cal a java.util.Calendar to increment - */ - private void increment(final Calendar cal) { - // initialise interval.. - final int calInterval = (getInterval() >= 1) ? getInterval() : 1; - cal.add(calIncField, calInterval); - } - - /** - * Returns a list of possible dates generated from the applicable BY* rules, using the specified date as a seed. - * @param date the seed date - * @param value the type of date list to return - * @return a DateList - */ - private DateList getCandidates(final Date date, final Value value) { - DateList dates = new DateList(value); - if (date instanceof DateTime) { - if (((DateTime) date).isUtc()) { - dates.setUtc(true); - } - else { - dates.setTimeZone(((DateTime) date).getTimeZone()); - } - } - dates.add(date); - dates = getMonthVariants(dates); - // debugging.. - if (log.isDebugEnabled()) { - log.debug("Dates after BYMONTH processing: " + dates); - } - dates = getWeekNoVariants(dates); - // debugging.. - if (log.isDebugEnabled()) { - log.debug("Dates after BYWEEKNO processing: " + dates); - } - dates = getYearDayVariants(dates); - // debugging.. - if (log.isDebugEnabled()) { - log.debug("Dates after BYYEARDAY processing: " + dates); - } - dates = getMonthDayVariants(dates); - // debugging.. - if (log.isDebugEnabled()) { - log.debug("Dates after BYMONTHDAY processing: " + dates); - } - dates = getDayVariants(dates); - // debugging.. - if (log.isDebugEnabled()) { - log.debug("Dates after BYDAY processing: " + dates); - } - dates = getHourVariants(dates); - // debugging.. - if (log.isDebugEnabled()) { - log.debug("Dates after BYHOUR processing: " + dates); - } - dates = getMinuteVariants(dates); - // debugging.. - if (log.isDebugEnabled()) { - log.debug("Dates after BYMINUTE processing: " + dates); - } - dates = getSecondVariants(dates); - // debugging.. - if (log.isDebugEnabled()) { - log.debug("Dates after BYSECOND processing: " + dates); - } - dates = applySetPosRules(dates); - // debugging.. - if (log.isDebugEnabled()) { - log.debug("Dates after SETPOS processing: " + dates); - } - return dates; - } - - /** - * Applies BYSETPOS rules todates
. Valid positions are from 1 to the size of the date list. Invalid
- * positions are ignored.
- * @param dates
- */
- private DateList applySetPosRules(final DateList dates) {
- // return if no SETPOS rules specified..
- if (getSetPosList().isEmpty()) {
- return dates;
- }
- // sort the list before processing..
- Collections.sort(dates);
- final DateList setPosDates = getDateListInstance(dates);
- final int size = dates.size();
- for (final Iterator i = getSetPosList().iterator(); i.hasNext();) {
- final Integer setPos = (Integer) i.next();
- final int pos = setPos.intValue();
- if (pos > 0 && pos <= size) {
- setPosDates.add(dates.get(pos - 1));
- }
- else if (pos < 0 && pos >= -size) {
- setPosDates.add(dates.get(size + pos));
- }
- }
- return setPosDates;
- }
-
- /**
- * Applies BYMONTH rules specified in this Recur instance to the specified date list. If no BYMONTH rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
- private DateList getMonthVariants(final DateList dates) {
- if (getMonthList().isEmpty()) {
- return dates;
- }
- final DateList monthlyDates = getDateListInstance(dates);
- for (final Iterator i = dates.iterator(); i.hasNext();) {
- final Date date = (Date) i.next();
- final Calendar cal = getCalendarInstance(date, true);
-
- for (final Iterator j = getMonthList().iterator(); j.hasNext();) {
- final Integer month = (Integer) j.next();
- // Java months are zero-based..
-// cal.set(Calendar.MONTH, month.intValue() - 1);
- cal.roll(Calendar.MONTH, (month.intValue() - 1) - cal.get(Calendar.MONTH));
- monthlyDates.add(Dates.getInstance(cal.getTime(), monthlyDates.getType()));
- }
- }
- return monthlyDates;
- }
-
- /**
- * Applies BYWEEKNO rules specified in this Recur instance to the specified date list. If no BYWEEKNO rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
- private DateList getWeekNoVariants(final DateList dates) {
- if (getWeekNoList().isEmpty()) {
- return dates;
- }
- final DateList weekNoDates = getDateListInstance(dates);
- for (final Iterator i = dates.iterator(); i.hasNext();) {
- final Date date = (Date) i.next();
- final Calendar cal = getCalendarInstance(date, true);
- for (final Iterator j = getWeekNoList().iterator(); j.hasNext();) {
- final Integer weekNo = (Integer) j.next();
- cal.set(Calendar.WEEK_OF_YEAR, Dates.getAbsWeekNo(cal.getTime(), weekNo.intValue()));
- weekNoDates.add(Dates.getInstance(cal.getTime(), weekNoDates.getType()));
- }
- }
- return weekNoDates;
- }
-
- /**
- * Applies BYYEARDAY rules specified in this Recur instance to the specified date list. If no BYYEARDAY rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
- private DateList getYearDayVariants(final DateList dates) {
- if (getYearDayList().isEmpty()) {
- return dates;
- }
- final DateList yearDayDates = getDateListInstance(dates);
- for (final Iterator i = dates.iterator(); i.hasNext();) {
- final Date date = (Date) i.next();
- final Calendar cal = getCalendarInstance(date, true);
- for (final Iterator j = getYearDayList().iterator(); j.hasNext();) {
- final Integer yearDay = (Integer) j.next();
- cal.set(Calendar.DAY_OF_YEAR, Dates.getAbsYearDay(cal.getTime(), yearDay.intValue()));
- yearDayDates.add(Dates.getInstance(cal.getTime(), yearDayDates.getType()));
- }
- }
- return yearDayDates;
- }
-
- /**
- * Applies BYMONTHDAY rules specified in this Recur instance to the specified date list. If no BYMONTHDAY rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
- private DateList getMonthDayVariants(final DateList dates) {
- if (getMonthDayList().isEmpty()) {
- return dates;
- }
- final DateList monthDayDates = getDateListInstance(dates);
- for (final Iterator i = dates.iterator(); i.hasNext();) {
- final Date date = (Date) i.next();
- final Calendar cal = getCalendarInstance(date, false);
- for (final Iterator j = getMonthDayList().iterator(); j.hasNext();) {
- final Integer monthDay = (Integer) j.next();
- try {
- cal.set(Calendar.DAY_OF_MONTH, Dates.getAbsMonthDay(cal.getTime(), monthDay.intValue()));
- monthDayDates.add(Dates.getInstance(cal.getTime(), monthDayDates.getType()));
- }
- catch (IllegalArgumentException iae) {
- if (log.isTraceEnabled()) {
- log.trace("Invalid day of month: " + Dates.getAbsMonthDay(cal
- .getTime(), monthDay.intValue()));
- }
- }
- }
- }
- return monthDayDates;
- }
-
- /**
- * Applies BYDAY rules specified in this Recur instance to the specified date list. If no BYDAY rules are specified
- * the date list is returned unmodified.
- * @param dates
- * @return
- */
- private DateList getDayVariants(final DateList dates) {
- if (getDayList().isEmpty()) {
- return dates;
- }
- final DateList weekDayDates = getDateListInstance(dates);
- for (final Iterator i = dates.iterator(); i.hasNext();) {
- final Date date = (Date) i.next();
- for (final Iterator j = getDayList().iterator(); j.hasNext();) {
- final WeekDay weekDay = (WeekDay) j.next();
- // if BYYEARDAY or BYMONTHDAY is specified filter existing
- // list..
- if (!getYearDayList().isEmpty() || !getMonthDayList().isEmpty()) {
- final Calendar cal = getCalendarInstance(date, true);
- if (weekDay.equals(WeekDay.getWeekDay(cal))) {
- weekDayDates.add(date);
- }
- }
- else {
- weekDayDates.addAll(getAbsWeekDays(date, dates.getType(), weekDay));
- }
- }
- }
- return weekDayDates;
- }
-
- /**
- * Returns a list of applicable dates corresponding to the specified week day in accordance with the frequency
- * specified by this recurrence rule.
- * @param date
- * @param weekDay
- * @return
- */
- private List getAbsWeekDays(final Date date, final Value type, final WeekDay weekDay) {
- final Calendar cal = getCalendarInstance(date, true);
- final DateList days = new DateList(type);
- if (date instanceof DateTime) {
- if (((DateTime) date).isUtc()) {
- days.setUtc(true);
- }
- else {
- days.setTimeZone(((DateTime) date).getTimeZone());
- }
- }
- final int calDay = WeekDay.getCalendarDay(weekDay);
- if (calDay == -1) {
- // a matching weekday cannot be identified..
- return days;
- }
- if (DAILY.equals(getFrequency())) {
- if (cal.get(Calendar.DAY_OF_WEEK) == calDay) {
- days.add(Dates.getInstance(cal.getTime(), type));
- }
- }
- else if (WEEKLY.equals(getFrequency()) || !getWeekNoList().isEmpty()) {
- final int weekNo = cal.get(Calendar.WEEK_OF_YEAR);
- // construct a list of possible week days..
- cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
- while (cal.get(Calendar.DAY_OF_WEEK) != calDay) {
- cal.add(Calendar.DAY_OF_WEEK, 1);
- }
-// final int weekNo = cal.get(Calendar.WEEK_OF_YEAR);
- if (cal.get(Calendar.WEEK_OF_YEAR) == weekNo) {
- days.add(Dates.getInstance(cal.getTime(), type));
-// cal.add(Calendar.DAY_OF_WEEK, Dates.DAYS_PER_WEEK);
- }
- }
- else if (MONTHLY.equals(getFrequency()) || !getMonthList().isEmpty()) {
- final int month = cal.get(Calendar.MONTH);
- // construct a list of possible month days..
- cal.set(Calendar.DAY_OF_MONTH, 1);
- while (cal.get(Calendar.DAY_OF_WEEK) != calDay) {
- cal.add(Calendar.DAY_OF_MONTH, 1);
- }
- while (cal.get(Calendar.MONTH) == month) {
- days.add(Dates.getInstance(cal.getTime(), type));
- cal.add(Calendar.DAY_OF_MONTH, Dates.DAYS_PER_WEEK);
- }
- }
- else if (YEARLY.equals(getFrequency())) {
- final int year = cal.get(Calendar.YEAR);
- // construct a list of possible year days..
- cal.set(Calendar.DAY_OF_YEAR, 1);
- while (cal.get(Calendar.DAY_OF_WEEK) != calDay) {
- cal.add(Calendar.DAY_OF_YEAR, 1);
- }
- while (cal.get(Calendar.YEAR) == year) {
- days.add(Dates.getInstance(cal.getTime(), type));
- cal.add(Calendar.DAY_OF_YEAR, Dates.DAYS_PER_WEEK);
- }
- }
- return getOffsetDates(days, weekDay.getOffset());
- }
-
- /**
- * Returns a single-element sublist containing the element of list
at offset
. Valid
- * offsets are from 1 to the size of the list. If an invalid offset is supplied, all elements from list
- * are added to sublist
.
- * @param list
- * @param offset
- * @param sublist
- */
- private List getOffsetDates(final DateList dates, final int offset) {
- if (offset == 0) {
- return dates;
- }
- final List offsetDates = getDateListInstance(dates);
- final int size = dates.size();
- if (offset < 0 && offset >= -size) {
- offsetDates.add(dates.get(size + offset));
- }
- else if (offset > 0 && offset <= size) {
- offsetDates.add(dates.get(offset - 1));
- }
- return offsetDates;
- }
-
- /**
- * Applies BYHOUR rules specified in this Recur instance to the specified date list. If no BYHOUR rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
- private DateList getHourVariants(final DateList dates) {
- if (getHourList().isEmpty()) {
- return dates;
- }
- final DateList hourlyDates = getDateListInstance(dates);
- for (final Iterator i = dates.iterator(); i.hasNext();) {
- final Date date = (Date) i.next();
- final Calendar cal = getCalendarInstance(date, true);
- for (final Iterator j = getHourList().iterator(); j.hasNext();) {
- final Integer hour = (Integer) j.next();
- cal.set(Calendar.HOUR_OF_DAY, hour.intValue());
- hourlyDates.add(Dates.getInstance(cal.getTime(), hourlyDates.getType()));
- }
- }
- return hourlyDates;
- }
-
- /**
- * Applies BYMINUTE rules specified in this Recur instance to the specified date list. If no BYMINUTE rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
- private DateList getMinuteVariants(final DateList dates) {
- if (getMinuteList().isEmpty()) {
- return dates;
- }
- final DateList minutelyDates = getDateListInstance(dates);
- for (final Iterator i = dates.iterator(); i.hasNext();) {
- final Date date = (Date) i.next();
- final Calendar cal = getCalendarInstance(date, true);
- for (final Iterator j = getMinuteList().iterator(); j.hasNext();) {
- final Integer minute = (Integer) j.next();
- cal.set(Calendar.MINUTE, minute.intValue());
- minutelyDates.add(Dates.getInstance(cal.getTime(), minutelyDates.getType()));
- }
- }
- return minutelyDates;
- }
-
- /**
- * Applies BYSECOND rules specified in this Recur instance to the specified date list. If no BYSECOND rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
- private DateList getSecondVariants(final DateList dates) {
- if (getSecondList().isEmpty()) {
- return dates;
- }
- final DateList secondlyDates = getDateListInstance(dates);
- for (final Iterator i = dates.iterator(); i.hasNext();) {
- final Date date = (Date) i.next();
- final Calendar cal = getCalendarInstance(date, true);
- for (final Iterator j = getSecondList().iterator(); j.hasNext();) {
- final Integer second = (Integer) j.next();
- cal.set(Calendar.SECOND, second.intValue());
- secondlyDates.add(Dates.getInstance(cal.getTime(), secondlyDates.getType()));
- }
- }
- return secondlyDates;
- }
-
- private void validateFrequency() {
- if (frequency == null) {
- throw new IllegalArgumentException(
- "A recurrence rule MUST contain a FREQ rule part.");
- }
- if (SECONDLY.equals(getFrequency())) {
- calIncField = Calendar.SECOND;
- }
- else if (MINUTELY.equals(getFrequency())) {
- calIncField = Calendar.MINUTE;
- }
- else if (HOURLY.equals(getFrequency())) {
- calIncField = Calendar.HOUR_OF_DAY;
- }
- else if (DAILY.equals(getFrequency())) {
- calIncField = Calendar.DAY_OF_YEAR;
- }
- else if (WEEKLY.equals(getFrequency())) {
- calIncField = Calendar.WEEK_OF_YEAR;
- }
- else if (MONTHLY.equals(getFrequency())) {
- calIncField = Calendar.MONTH;
- }
- else if (YEARLY.equals(getFrequency())) {
- calIncField = Calendar.YEAR;
- }
- else {
- throw new IllegalArgumentException("Invalid FREQ rule part '"
- + frequency + "' in recurrence rule");
- }
- }
-
- /**
- * @param count The count to set.
- */
- public final void setCount(final int count) {
- this.count = count;
- this.until = null;
- }
-
- /**
- * @param frequency The frequency to set.
- */
- public final void setFrequency(final String frequency) {
- this.frequency = frequency;
- validateFrequency();
- }
-
- /**
- * @param interval The interval to set.
- */
- public final void setInterval(final int interval) {
- this.interval = interval;
- }
-
- /**
- * @param until The until to set.
- */
- public final void setUntil(final Date until) {
- this.until = until;
- this.count = -1;
- }
-
- /**
- * Construct a Calendar object and sets the time.
- * @param date
- * @param lenient
- * @return
- */
- private Calendar getCalendarInstance(final Date date, final boolean lenient) {
- Calendar cal = Dates.getCalendarInstance(date);
- // A week should have at least 4 days to be considered as such per RFC5545
- cal.setMinimalDaysInFirstWeek(4);
- cal.setFirstDayOfWeek(calendarWeekStartDay);
- cal.setLenient(lenient);
- cal.setTime(date);
-
- return cal;
- }
-
- /**
- * @param stream
- * @throws IOException
- * @throws ClassNotFoundException
- */
- private void readObject(final java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException {
- stream.defaultReadObject();
- log = LogFactory.getLog(Recur.class);
- }
-
- /**
- * Instantiate a new datelist with the same type, timezone and utc settings
- * as the origList.
- * @param origList
- * @return a new empty list.
- */
- private static DateList getDateListInstance(final DateList origList) {
- final DateList list = new DateList(origList.getType());
- if (origList.isUtc()) {
- list.setUtc(true);
- } else {
- list.setTimeZone(origList.getTimeZone());
- }
- return list;
- }
-
-}