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

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

/**
 * 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.transform.Transformer;
import net.fortuna.ical4j.transform.recurrence.*;
import net.fortuna.ical4j.util.CompatibilityHints;
import net.fortuna.ical4j.util.Configurator;
import net.fortuna.ical4j.util.Dates;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.Serializable;
import java.text.ParseException;
import java.util.Calendar;
import java.util.*;

/**
 * $Id$ [18-Apr-2004]
 * 

* Defines a recurrence. * * @author Ben Fortuna * @version 2.0 */ 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"; public enum Frequency { SECONDLY, MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY; } /** * Second frequency resolution. * @deprecated use {@link Frequency} instead. */ @Deprecated public static final String SECONDLY = "SECONDLY"; /** * Minute frequency resolution. * @deprecated use {@link Frequency} instead. */ @Deprecated public static final String MINUTELY = "MINUTELY"; /** * Hour frequency resolution. * @deprecated use {@link Frequency} instead. */ @Deprecated public static final String HOURLY = "HOURLY"; /** * Day frequency resolution. * @deprecated use {@link Frequency} instead. */ @Deprecated public static final String DAILY = "DAILY"; /** * Week frequency resolution. * @deprecated use {@link Frequency} instead. */ @Deprecated public static final String WEEKLY = "WEEKLY"; /** * Month frequency resolution. * @deprecated use {@link Frequency} instead. */ @Deprecated public static final String MONTHLY = "MONTHLY"; /** * Year frequency resolution. * @deprecated use {@link Frequency} instead. */ @Deprecated 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 { maxIncrementCount = Configurator.getIntProperty(KEY_MAX_INCREMENT_COUNT).orElse(1000); } private transient Logger log = LoggerFactory.getLogger(Recur.class); private Frequency frequency; private Date until; private Integer count; private Integer interval; 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 Map> transformers; private WeekDay.Day weekStartDay; private int calendarWeekStartDay; private Map experimentalValues = new HashMap(); // Calendar field we increment based on frequency. private int calIncField; /** * Default constructor. */ private Recur() { // default week start is Monday per RFC5545 calendarWeekStartDay = Calendar.MONDAY; initTransformers(); } /** * 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 where the recurrence string contains an unrecognised token */ public Recur(final String aValue) throws ParseException { // default week start is Monday per RFC5545 calendarWeekStartDay = Calendar.MONDAY; Iterator tokens = Arrays.asList(aValue.split("[;=]")).iterator(); while (tokens.hasNext()) { final String token = tokens.next(); if (FREQ.equals(token)) { frequency = Frequency.valueOf(nextToken(tokens, token)); } else if (UNTIL.equals(token)) { final String untilString = nextToken(tokens, token); if (untilString != null && untilString.contains("T")) { 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(tokens, token)); } else if (INTERVAL.equals(token)) { interval = Integer.parseInt(nextToken(tokens, token)); } else if (BYSECOND.equals(token)) { secondList = new NumberList(nextToken(tokens, token), 0, 59, false); } else if (BYMINUTE.equals(token)) { minuteList = new NumberList(nextToken(tokens, token), 0, 59, false); } else if (BYHOUR.equals(token)) { hourList = new NumberList(nextToken(tokens, token), 0, 23, false); } else if (BYDAY.equals(token)) { dayList = new WeekDayList(nextToken(tokens, token)); } else if (BYMONTHDAY.equals(token)) { monthDayList = new NumberList(nextToken(tokens, token), 1, 31, true); } else if (BYYEARDAY.equals(token)) { yearDayList = new NumberList(nextToken(tokens, token), 1, 366, true); } else if (BYWEEKNO.equals(token)) { weekNoList = new NumberList(nextToken(tokens, token), 1, 53, true); } else if (BYMONTH.equals(token)) { monthList = new NumberList(nextToken(tokens, token), 1, 12, false); } else if (BYSETPOS.equals(token)) { setPosList = new NumberList(nextToken(tokens, token), 1, 366, true); } else if (WKST.equals(token)) { weekStartDay = WeekDay.Day.valueOf(nextToken(tokens, token)); calendarWeekStartDay = WeekDay.getCalendarDay(WeekDay.getWeekDay(weekStartDay)); } else { if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING)) { // assume experimental value.. experimentalValues.put(token, nextToken(tokens, token)); } else { throw new IllegalArgumentException(String.format("Invalid recurrence rule part: %s=%s", token, nextToken(tokens, token))); } } } validateFrequency(); initTransformers(); } private String nextToken(Iterator tokens, String lastToken) { try { return tokens.next(); } catch (NoSuchElementException e) { throw new IllegalArgumentException("Missing expected token, last token: " + lastToken); } } /** * @param frequency a recurrence frequency string * @param until maximum recurrence date */ @Deprecated public Recur(final String frequency, final Date until) { this(Frequency.valueOf(frequency), until); } /** * @param frequency a recurrence frequency string * @param until maximum recurrence date */ public Recur(final Frequency frequency, final Date until) { // default week start is Monday per RFC5545 calendarWeekStartDay = Calendar.MONDAY; this.frequency = frequency; this.until = until; validateFrequency(); initTransformers(); } /** * @param frequency a recurrence frequency string * @param count maximum recurrence count */ @Deprecated public Recur(final String frequency, final int count) { this(Frequency.valueOf(frequency), count); } /** * @param frequency a recurrence frequency string * @param count maximum recurrence count */ public Recur(final Frequency frequency, final int count) { // default week start is Monday per RFC5545 calendarWeekStartDay = Calendar.MONDAY; this.frequency = frequency; this.count = count; validateFrequency(); initTransformers(); } private void initTransformers() { transformers = new HashMap<>(); if (secondList != null) { transformers.put(BYSECOND, new BySecondRule(secondList, frequency, Optional.ofNullable(weekStartDay))); } else { secondList = new NumberList(0, 59, false); } if (minuteList != null) { transformers.put(BYMINUTE, new ByMinuteRule(minuteList, frequency, Optional.ofNullable(weekStartDay))); } else { minuteList = new NumberList(0, 59, false); } if (hourList != null) { transformers.put(BYHOUR, new ByHourRule(hourList, frequency, Optional.ofNullable(weekStartDay))); } else { hourList = new NumberList(0, 23, false); } if (monthDayList != null) { transformers.put(BYMONTHDAY, new ByMonthDayRule(monthDayList, frequency, Optional.ofNullable(weekStartDay))); } else { monthDayList = new NumberList(1, 31, true); } if (yearDayList != null) { transformers.put(BYYEARDAY, new ByYearDayRule(yearDayList, frequency, Optional.ofNullable(weekStartDay))); } else { yearDayList = new NumberList(1, 366, true); } if (weekNoList != null) { transformers.put(BYWEEKNO, new ByWeekNoRule(weekNoList, frequency, Optional.ofNullable(weekStartDay))); } else { weekNoList = new NumberList(1, 53, true); } if (monthList != null) { transformers.put(BYMONTH, new ByMonthRule(monthList, frequency, Optional.ofNullable(weekStartDay))); } else { monthList = new NumberList(1, 12, false); } if (dayList != null) { transformers.put(BYDAY, new ByDayRule(dayList, deriveFilterType(), Optional.ofNullable(weekStartDay))); } else { dayList = new WeekDayList(); } if (setPosList != null) { transformers.put(BYSETPOS, new BySetPosRule(setPosList)); } else { setPosList = new NumberList(1, 366, true); } } private Frequency deriveFilterType() { if (frequency == Frequency.DAILY || !getYearDayList().isEmpty() || !getMonthDayList().isEmpty()) { return Frequency.DAILY; } else if (frequency == Frequency.WEEKLY || !getWeekNoList().isEmpty()) { return Frequency.WEEKLY; } else if (frequency == Frequency.MONTHLY || !getMonthList().isEmpty()) { return Frequency.MONTHLY; } else { return frequency; } } /** * Accessor for the configured BYDAY list. * NOTE: Any changes to the returned list will have no effect on the recurrence rule processing. * * @return Returns the dayList. */ public final WeekDayList getDayList() { return dayList; } /** * Accessor for the configured BYHOUR list. * NOTE: Any changes to the returned list will have no effect on the recurrence rule processing. * * @return Returns the hourList. */ public final NumberList getHourList() { return hourList; } /** * Accessor for the configured BYMINUTE list. * NOTE: Any changes to the returned list will have no effect on the recurrence rule processing. * * @return Returns the minuteList. */ public final NumberList getMinuteList() { return minuteList; } /** * Accessor for the configured BYMONTHDAY list. * NOTE: Any changes to the returned list will have no effect on the recurrence rule processing. * * @return Returns the monthDayList. */ public final NumberList getMonthDayList() { return monthDayList; } /** * Accessor for the configured BYMONTH list. * NOTE: Any changes to the returned list will have no effect on the recurrence rule processing. * * @return Returns the monthList. */ public final NumberList getMonthList() { return monthList; } /** * Accessor for the configured BYSECOND list. * NOTE: Any changes to the returned list will have no effect on the recurrence rule processing. * * @return Returns the secondList. */ public final NumberList getSecondList() { return secondList; } /** * Accessor for the configured BYSETPOS list. * NOTE: Any changes to the returned list will have no effect on the recurrence rule processing. * * @return Returns the setPosList. */ public final NumberList getSetPosList() { return setPosList; } /** * Accessor for the configured BYWEEKNO list. * NOTE: Any changes to the returned list will have no effect on the recurrence rule processing. * * @return Returns the weekNoList. */ public final NumberList getWeekNoList() { return weekNoList; } /** * Accessor for the configured BYYEARDAY list. * NOTE: Any changes to the returned list will have no effect on the recurrence rule processing. * * @return Returns the yearDayList. */ public final NumberList getYearDayList() { return yearDayList; } /** * @return Returns the count or -1 if the rule does not have a count. */ public final int getCount() { return Optional.ofNullable(count).orElse(-1); } /** * @return Returns the experimentalValues. */ public final Map getExperimentalValues() { return experimentalValues; } /** * @return Returns the frequency. */ public final Frequency getFrequency() { return frequency; } /** * @return Returns the interval or -1 if the rule does not have an interval defined. */ public final int getInterval() { return Optional.ofNullable(interval).orElse(-1); } /** * @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 WeekDay.Day getWeekStartDay() { return weekStartDay; } /** * @param weekStartDay The weekStartDay to set. * @deprecated will be removed in a future version to support immutable pattern. */ @Deprecated public final void setWeekStartDay(final WeekDay.Day weekStartDay) { this.weekStartDay = weekStartDay; if (weekStartDay != null) { calendarWeekStartDay = WeekDay.getCalendarDay(WeekDay.getWeekDay(weekStartDay)); } } /** * {@inheritDoc} */ @Override public final String toString() { final StringBuilder b = new StringBuilder(); 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 != null) { b.append(';'); b.append(COUNT); b.append('='); b.append(count); } if (interval != null) { b.append(';'); b.append(INTERVAL); b.append('='); b.append(interval); } if (!monthList.isEmpty()) { b.append(';'); b.append(BYMONTH); b.append('='); b.append(monthList); } if (!weekNoList.isEmpty()) { b.append(';'); b.append(BYWEEKNO); b.append('='); b.append(weekNoList); } if (!yearDayList.isEmpty()) { b.append(';'); b.append(BYYEARDAY); b.append('='); b.append(yearDayList); } if (!monthDayList.isEmpty()) { b.append(';'); b.append(BYMONTHDAY); b.append('='); b.append(monthDayList); } if (!dayList.isEmpty()) { b.append(';'); b.append(BYDAY); b.append('='); b.append(dayList); } if (!hourList.isEmpty()) { b.append(';'); b.append(BYHOUR); b.append('='); b.append(hourList); } if (!minuteList.isEmpty()) { b.append(';'); b.append(BYMINUTE); b.append('='); b.append(minuteList); } if (!secondList.isEmpty()) { b.append(';'); b.append(BYSECOND); b.append('='); b.append(secondList); } if (!setPosList.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. * * @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) * @return a list of dates represented by this recur instance */ 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. * * @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 * @return a list of dates represented by this recur instance */ 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); final Calendar rootSeed = (Calendar)cal.clone(); // optimize the start time for selecting candidates // (only applicable where a COUNT is not specified) if (count == null) { final Calendar seededCal = (Calendar) cal.clone(); while (seededCal.getTime().before(periodStart)) { cal.setTime(seededCal.getTime()); increment(seededCal); } } HashSet invalidCandidates = new HashSet(); 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() + invalidCandidates.size()) >= 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()); } } // rootSeed = date used for the seed for the RRule at the // start of the first period. // candidateSeed = date used for the start of // the current period. final DateList candidates = getCandidates(rootSeed, candidateSeed, value); if (!candidates.isEmpty()) { noCandidateIncrementCount = 0; // sort candidates for identifying when UNTIL date is exceeded.. Collections.sort(candidates); for (Date candidate1 : candidates) { candidate = candidate1; // don't count candidates that occur before the seed date.. if (!candidate.before(seed)) { // candidates exclusive of periodEnd.. if (candidate.before(periodStart) || candidate.after(periodEnd)) { invalidCandidates.add(candidate); } else if (getCount() >= 1 && (dates.size() + invalidCandidates.size()) >= getCount()) { break; } else if (!candidate.before(periodStart) && !candidate.after(periodEnd) && (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. * * @param seed the start date of this Recurrence's first instance * @param startDate the date to start the search * @return the next date in the recurrence series after startDate */ public final Date getNextDate(final Date seed, final Date startDate) { final Calendar cal = getCalendarInstance(seed, true); final Calendar rootSeed = (Calendar)cal.clone(); // optimize the start time for selecting candidates // (only applicable where a COUNT is not specified) if (count == null) { 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(rootSeed, candidateSeed, value); if (!candidates.isEmpty()) { noCandidateIncrementCount = 0; // sort candidates for identifying when UNTIL date is exceeded.. Collections.sort(candidates); for (Date candidate1 : candidates) { candidate = candidate1; // 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 Calendar rootSeed, 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); if (transformers.get(BYMONTH) != null) { dates = transformers.get(BYMONTH).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYMONTH processing: " + dates); } } if (transformers.get(BYWEEKNO) != null) { dates = transformers.get(BYWEEKNO).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYWEEKNO processing: " + dates); } } if (transformers.get(BYYEARDAY) != null) { dates = transformers.get(BYYEARDAY).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYYEARDAY processing: " + dates); } } if (transformers.get(BYMONTHDAY) != null) { dates = transformers.get(BYMONTHDAY).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYMONTHDAY processing: " + dates); } } else if ((frequency == Frequency.MONTHLY && dayList.isEmpty()) || (frequency == Frequency.YEARLY && yearDayList.isEmpty() && weekNoList.isEmpty() && dayList.isEmpty())) { NumberList implicitMonthDayList = new NumberList(); implicitMonthDayList.add(rootSeed.get(Calendar.DAY_OF_MONTH)); ByMonthDayRule implicitRule = new ByMonthDayRule(implicitMonthDayList, frequency, Optional.ofNullable(weekStartDay)); dates = implicitRule.transform(dates); } if (transformers.get(BYDAY) != null) { dates = transformers.get(BYDAY).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYDAY processing: " + dates); } } else if (frequency == Frequency.WEEKLY || (frequency == Frequency.YEARLY && yearDayList.isEmpty() && !weekNoList.isEmpty() && monthDayList.isEmpty())) { ByDayRule implicitRule = new ByDayRule(new WeekDayList(WeekDay.getWeekDay(rootSeed)), deriveFilterType(), Optional.ofNullable(weekStartDay)); dates = implicitRule.transform(dates); } if (transformers.get(BYHOUR) != null) { dates = transformers.get(BYHOUR).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYHOUR processing: " + dates); } } if (transformers.get(BYMINUTE) != null) { dates = transformers.get(BYMINUTE).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYMINUTE processing: " + dates); } } if (transformers.get(BYSECOND) != null) { dates = transformers.get(BYSECOND).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after BYSECOND processing: " + dates); } } if (transformers.get(BYSETPOS) != null) { dates = transformers.get(BYSETPOS).transform(dates); // debugging.. if (log.isDebugEnabled()) { log.debug("Dates after SETPOS processing: " + dates); } } return dates; } private void validateFrequency() { if (frequency == null) { throw new IllegalArgumentException("A recurrence rule MUST contain a FREQ rule part."); } if (Frequency.SECONDLY.equals(getFrequency())) { calIncField = Calendar.SECOND; } else if (Frequency.MINUTELY.equals(getFrequency())) { calIncField = Calendar.MINUTE; } else if (Frequency.HOURLY.equals(getFrequency())) { calIncField = Calendar.HOUR_OF_DAY; } else if (Frequency.DAILY.equals(getFrequency())) { calIncField = Calendar.DAY_OF_YEAR; } else if (Frequency.WEEKLY.equals(getFrequency())) { calIncField = Calendar.WEEK_OF_YEAR; } else if (Frequency.MONTHLY.equals(getFrequency())) { calIncField = Calendar.MONTH; } else if (Frequency.YEARLY.equals(getFrequency())) { calIncField = Calendar.YEAR; } else { throw new IllegalArgumentException("Invalid FREQ rule part '" + frequency + "' in recurrence rule"); } } /** * @param count The count to set. * @deprecated will be removed in a future version to support immutable pattern. */ @Deprecated public final void setCount(final int count) { this.count = count; this.until = null; } /** * @param frequency The frequency to set. * @deprecated will be removed in a future version to support immutable pattern. */ @Deprecated public final void setFrequency(final String frequency) { this.frequency = Frequency.valueOf(frequency); validateFrequency(); } /** * @param interval The interval to set. * @deprecated will be removed in a future version to support immutable pattern. */ @Deprecated public final void setInterval(final int interval) { this.interval = interval; } /** * @param until The until to set. * @deprecated will be removed in a future version to support immutable pattern. */ @Deprecated 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 = LoggerFactory.getLogger(Recur.class); } /** * Support for building Recur instances. */ public static class Builder { private Frequency frequency; private Date until; private Integer count; private Integer interval; 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 WeekDay.Day weekStartDay; public Builder frequency(Frequency frequency) { this.frequency = frequency; return this; } public Builder until(Date until) { this.until = until; return this; } public Builder count(Integer count) { this.count = count; return this; } public Builder interval(Integer interval) { this.interval = interval; return this; } public Builder secondList(NumberList secondList) { this.secondList = secondList; return this; } public Builder minuteList(NumberList minuteList) { this.minuteList = minuteList; return this; } public Builder hourList(NumberList hourList) { this.hourList = hourList; return this; } public Builder dayList(WeekDayList dayList) { this.dayList = dayList; return this; } public Builder monthDayList(NumberList monthDayList) { this.monthDayList = monthDayList; return this; } public Builder yearDayList(NumberList yearDayList) { this.yearDayList = yearDayList; return this; } public Builder weekNoList(NumberList weekNoList) { this.weekNoList = weekNoList; return this; } public Builder monthList(NumberList monthList) { this.monthList = monthList; return this; } public Builder setPosList(NumberList setPosList) { this.setPosList = setPosList; return this; } public Builder weekStartDay(WeekDay.Day weekStartDay) { this.weekStartDay = weekStartDay; return this; } public Recur build() { Recur recur = new Recur(); recur.frequency = frequency; recur.until = until; recur.count = count; recur.interval = interval; recur.secondList = secondList; recur.minuteList = minuteList; recur.hourList = hourList; recur.dayList = dayList; recur.monthDayList = monthDayList; recur.yearDayList = yearDayList; recur.weekNoList = weekNoList; recur.monthList = monthList; recur.setPosList = setPosList; recur.weekStartDay = weekStartDay; recur.validateFrequency(); recur.initTransformers(); return recur; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy