/* * $Id: Recur.java,v 1.28 2006/06/17 01:59:37 fortuna Exp $ [18-Apr-2004] * * Copyright (c) 2004, 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.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.StringTokenizer; import net.fortuna.ical4j.model.parameter.Value; import net.fortuna.ical4j.util.Dates; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * 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"; // frequencies.. public static final String SECONDLY = "SECONDLY"; public static final String MINUTELY = "MINUTELY"; public static final String HOURLY = "HOURLY"; public static final String DAILY = "DAILY"; public static final String WEEKLY = "WEEKLY"; public static final String MONTHLY = "MONTHLY"; public static final String YEARLY = "YEARLY"; private 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 Map experimentalValues = new HashMap(); /** * 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 * @throws IllegalArgumentException if the <code>frequency</code> is <code>null</code> or unrecognized. */ public Recur(final String aValue) throws ParseException { for (StringTokenizer t = new StringTokenizer(aValue, ";="); t .hasMoreTokens();) { String token = t.nextToken(); if (FREQ.equals(token)) { frequency = t.nextToken(); } else if (UNTIL.equals(token)) { String untilString = t.nextToken(); try { until = new DateTime(untilString); // UNTIL must be specified in UTC time.. ((DateTime) until).setUtc(true); } catch (ParseException pe) { until = new Date(untilString); } } else if (COUNT.equals(token)) { count = Integer.parseInt(t.nextToken()); } else if (INTERVAL.equals(token)) { interval = Integer.parseInt(t.nextToken()); } else if (BYSECOND.equals(token)) { secondList = new NumberList(t.nextToken()); } else if (BYMINUTE.equals(token)) { minuteList = new NumberList(t.nextToken()); } else if (BYHOUR.equals(token)) { hourList = new NumberList(t.nextToken()); } else if (BYDAY.equals(token)) { dayList = new WeekDayList(t.nextToken()); } else if (BYMONTHDAY.equals(token)) { monthDayList = new NumberList(t.nextToken()); } else if (BYYEARDAY.equals(token)) { yearDayList = new NumberList(t.nextToken()); } else if (BYWEEKNO.equals(token)) { weekNoList = new NumberList(t.nextToken()); } else if (BYMONTH.equals(token)) { monthList = new NumberList(t.nextToken()); } else if (BYSETPOS.equals(token)) { setPosList = new NumberList(t.nextToken()); } else if (WKST.equals(token)) { weekStartDay = t.nextToken(); } // assume experimental value.. else { experimentalValues.put(token, t.nextToken()); } } validateFrequency(); } /** * @param frequency * @param until * @throws IllegalArgumentException if the <code>frequency</code> is <code>null</code> or unrecognized. */ public Recur(final String frequency, final Date until) { this.frequency = frequency; this.until = until; validateFrequency(); } /** * @param frequency * @param count * @throws IllegalArgumentException if the <code>frequency</code> is <code>null</code> or unrecognized. */ public Recur(final String frequency, final int count) { 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(); } return hourList; } /** * @return Returns the minuteList. */ public final NumberList getMinuteList() { if (minuteList == null) { minuteList = new NumberList(); } return minuteList; } /** * @return Returns the monthDayList. */ public final NumberList getMonthDayList() { if (monthDayList == null) { monthDayList = new NumberList(); } return monthDayList; } /** * @return Returns the monthList. */ public final NumberList getMonthList() { if (monthList == null) { monthList = new NumberList(); } return monthList; } /** * @return Returns the secondList. */ public final NumberList getSecondList() { if (secondList == null) { secondList = new NumberList(); } return secondList; } /** * @return Returns the setPosList. */ public final NumberList getSetPosList() { if (setPosList == null) { setPosList = new NumberList(); } return setPosList; } /** * @return Returns the weekNoList. */ public final NumberList getWeekNoList() { if (weekNoList == null) { weekNoList = new NumberList(); } return weekNoList; } /** * @return Returns the yearDayList. */ public final NumberList getYearDayList() { if (yearDayList == null) { yearDayList = new NumberList(); } return yearDayList; } /** * @return Returns the 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. */ public final int getInterval() { return interval; } /** * @return Returns the until. */ public final Date getUntil() { return until; } /** * @return Returns the weekStartDay. */ public final String getWeekStartDay() { return weekStartDay; } /** * @param weekStartDay The weekStartDay to set. */ public final void setWeekStartDay(final String weekStartDay) { this.weekStartDay = weekStartDay; } /** * @see java.lang.Object#toString() */ public final String toString() { 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 (interval >= 1) { b.append(';'); b.append(INTERVAL); b.append('='); b.append(interval); } 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 (!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); } /** * Convenience method for retrieving recurrences in a specified period. * @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); } /** * 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 base 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) { DateList dates = new DateList(value); if (seed instanceof DateTime) { if (((DateTime) seed).isUtc()) { dates.setUtc(true); } else { dates.setTimeZone(((DateTime) seed).getTimeZone()); } } Calendar cal = Dates.getCalendarInstance(seed); cal.setTime(seed); // optimize the start time for selecting candidates // (only applicable where a COUNT is not specified) if (getCount() < 1) { Calendar seededCal = (Calendar) cal.clone(); while (seededCal.getTime().before(periodStart)) { cal.setTime(seededCal.getTime()); increment(seededCal); } } int invalidCandidateCount = 0; while (!((getUntil() != null && cal.getTime().after(getUntil())) || (periodEnd != null && cal.getTime().after(periodEnd)) || (getCount() >= 1 && (dates.size() + invalidCandidateCount) >= getCount()))) { Date candidateSeed = Dates.getInstance(cal.getTime(), value); if (Value.DATE_TIME.equals(value)) { if (dates.isUtc()) { ((DateTime) candidateSeed).setUtc(true); } else { ((DateTime) candidateSeed).setTimeZone(dates.getTimeZone()); } } DateList candidates = getCandidates(candidateSeed, value); for (Iterator i = candidates.iterator(); i.hasNext();) { Date 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); } } } increment(cal); } // sort final list.. Collections.sort(dates); return dates; } /** * 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.. int calInterval = (getInterval() >= 1) ? getInterval() : 1; if (SECONDLY.equals(getFrequency())) { cal.add(Calendar.SECOND, calInterval); } else if (MINUTELY.equals(getFrequency())) { cal.add(Calendar.MINUTE, calInterval); } else if (HOURLY.equals(getFrequency())) { cal.add(Calendar.HOUR_OF_DAY, calInterval); } else if (DAILY.equals(getFrequency())) { cal.add(Calendar.DAY_OF_YEAR, calInterval); } else if (WEEKLY.equals(getFrequency())) { cal.add(Calendar.WEEK_OF_YEAR, calInterval); } else if (MONTHLY.equals(getFrequency())) { cal.add(Calendar.MONTH, calInterval); } else if (YEARLY.equals(getFrequency())) { cal.add(Calendar.YEAR, 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 to <code>dates</code>. 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); DateList setPosDates = new DateList(dates.getType(), dates.getTimeZone()); int size = dates.size(); for (Iterator i = getSetPosList().iterator(); i.hasNext();) { Integer setPos = (Integer) i.next(); 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; } DateList monthlyDates = new DateList(dates.getType(), dates.getTimeZone()); for (Iterator i = dates.iterator(); i.hasNext();) { Date date = (Date) i.next(); Calendar cal = Dates.getCalendarInstance(date); cal.setTime(date); for (Iterator j = getMonthList().iterator(); j.hasNext();) { Integer month = (Integer) j.next(); // Java months are zero-based.. cal.set(Calendar.MONTH, month.intValue() - 1); 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; } DateList weekNoDates = new DateList(dates.getType(), dates.getTimeZone()); for (Iterator i = dates.iterator(); i.hasNext();) { Date date = (Date) i.next(); Calendar cal = Dates.getCalendarInstance(date); cal.setTime(date); for (Iterator j = getWeekNoList().iterator(); j.hasNext();) { 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; } DateList yearDayDates = new DateList(dates.getType(), dates.getTimeZone()); for (Iterator i = dates.iterator(); i.hasNext();) { Date date = (Date) i.next(); Calendar cal = Dates.getCalendarInstance(date); cal.setTime(date); for (Iterator j = getYearDayList().iterator(); j.hasNext();) { 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; } DateList monthDayDates = new DateList(dates.getType(), dates.getTimeZone()); for (Iterator i = dates.iterator(); i.hasNext();) { Date date = (Date) i.next(); Calendar cal = Dates.getCalendarInstance(date); cal.setTime(date); for (Iterator j = getMonthDayList().iterator(); j.hasNext();) { Integer monthDay = (Integer) j.next(); cal.set(Calendar.DAY_OF_MONTH, Dates.getAbsMonthDay(cal.getTime(), monthDay.intValue())); monthDayDates.add(Dates.getInstance(cal.getTime(), monthDayDates.getType())); } } 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; } DateList weekDayDates = new DateList(dates.getType(), dates.getTimeZone()); for (Iterator i = dates.iterator(); i.hasNext();) { Date date = (Date) i.next(); for (Iterator j = getDayList().iterator(); j.hasNext();) { WeekDay weekDay = (WeekDay) j.next(); // if BYYEARDAY or BYMONTHDAY is specified filter existing list.. if (!getYearDayList().isEmpty() || !getMonthDayList().isEmpty()) { Calendar cal = Calendar.getInstance(); cal.setTime(date); 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) { Calendar cal = Dates.getCalendarInstance(date); cal.setTime(date); DateList days = new DateList(type); if (date instanceof DateTime) { if (((DateTime) date).isUtc()) { days.setUtc(true); } else { days.setTimeZone(((DateTime) date).getTimeZone()); } } 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()) { //int weekNo = cal.get(Calendar.WEEK_OF_YEAR); // construct a list of possible week days.. // cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, 1); while (cal.get(Calendar.DAY_OF_WEEK) != calDay) { cal.add(Calendar.DAY_OF_WEEK, 1); } int weekNo = cal.get(Calendar.WEEK_OF_YEAR); while (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()) { 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())) { 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 * <code>list</code> at <code>offset</code>. Valid offsets are from 1 * to the size of the list. If an invalid offset is supplied, all elements * from <code>list</code> are added to <code>sublist</code>. * * @param list * @param offset * @param sublist */ private List getOffsetDates(final DateList dates, final int offset) { if (offset == 0) { return dates; } List offsetDates = new DateList(dates.getType(), dates.getTimeZone()); 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; } DateList hourlyDates = new DateList(dates.getType(), dates.getTimeZone()); for (Iterator i = dates.iterator(); i.hasNext();) { Date date = (Date) i.next(); Calendar cal = Dates.getCalendarInstance(date); cal.setTime(date); for (Iterator j = getHourList().iterator(); j.hasNext();) { 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; } DateList minutelyDates = new DateList(dates.getType(), dates.getTimeZone()); for (Iterator i = dates.iterator(); i.hasNext();) { Date date = (Date) i.next(); Calendar cal = Dates.getCalendarInstance(date); cal.setTime(date); for (Iterator j = getMinuteList().iterator(); j.hasNext();) { 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; } DateList secondlyDates = new DateList(dates.getType(), dates.getTimeZone()); for (Iterator i = dates.iterator(); i.hasNext();) { Date date = (Date) i.next(); Calendar cal = Dates.getCalendarInstance(date); cal.setTime(date); for (Iterator j = getSecondList().iterator(); j.hasNext();) { 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."); } else if (!frequency.equals(SECONDLY) && !frequency.equals(MINUTELY) && !frequency.equals(HOURLY) && !frequency.equals(DAILY) && !frequency.equals(WEEKLY) && !frequency.equals(MONTHLY) && !frequency.equals(YEARLY)) { 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. * @throws IllegalArgumentException if the <code>frequency</code> is <code>null</code> or unrecognized. */ 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; } }