All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.fortuna.ical4j.model.Recur Maven / Gradle / Ivy

The newest version!
/**
 * 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 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;

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;

/**
 * $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 to dates. 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 offset */ 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; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy