/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
/*
* Copyright (c) 2000, Columbia University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. Neither the name of the University nor the names of its 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 HOLDERS 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 com.liferay.portal.kernel.cal;
import com.liferay.portal.kernel.util.ArrayUtil;
import com.liferay.portal.kernel.util.CalendarFactoryUtil;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.TimeZoneUtil;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
/**
* @author Jonathan Lennox
*/
public class Recurrence implements Serializable {
/**
* Field DAILY
*/
public static final int DAILY = 3;
/**
* Field MONTHLY
*/
public static final int MONTHLY = 5;
/**
* Field NO_RECURRENCE
*/
public static final int NO_RECURRENCE = 7;
/**
* Field WEEKLY
*/
public static final int WEEKLY = 4;
/**
* Field YEARLY
*/
public static final int YEARLY = 6;
/**
* Constructor Recurrence
*/
public Recurrence() {
this(null, new Duration(), NO_RECURRENCE);
}
/**
* Constructor Recurrence
*/
public Recurrence(Calendar start, Duration dur) {
this(start, dur, NO_RECURRENCE);
}
/**
* Constructor Recurrence
*/
public Recurrence(Calendar start, Duration dur, int freq) {
setDtStart(start);
duration = (Duration)dur.clone();
frequency = freq;
interval = 1;
}
// Accessors
/**
* Method getByDay
*
* @return DayAndPosition[]
*/
public DayAndPosition[] getByDay() {
if (byDay == null) {
return null;
}
DayAndPosition[] b = new DayAndPosition[byDay.length];
/*
* System.arraycopy isn't good enough -- we want to clone each
* individual element.
*/
for (int i = 0; i < byDay.length; i++) {
b[i] = (DayAndPosition)byDay[i].clone();
}
return b;
}
/**
* Method getByMonth
*
* @return int[]
*/
public int[] getByMonth() {
if (byMonth == null) {
return null;
}
int[] b = new int[byMonth.length];
System.arraycopy(byMonth, 0, b, 0, byMonth.length);
return b;
}
/**
* Method getByMonthDay
*
* @return int[]
*/
public int[] getByMonthDay() {
if (byMonthDay == null) {
return null;
}
int[] b = new int[byMonthDay.length];
System.arraycopy(byMonthDay, 0, b, 0, byMonthDay.length);
return b;
}
/**
* Method getByWeekNo
*
* @return int[]
*/
public int[] getByWeekNo() {
if (byWeekNo == null) {
return null;
}
int[] b = new int[byWeekNo.length];
System.arraycopy(byWeekNo, 0, b, 0, byWeekNo.length);
return b;
}
/**
* Method getByYearDay
*
* @return int[]
*/
public int[] getByYearDay() {
if (byYearDay == null) {
return null;
}
int[] b = new int[byYearDay.length];
System.arraycopy(byYearDay, 0, b, 0, byYearDay.length);
return b;
}
/**
* Method getCandidateStartTime
*
* @param current the current time
* @return Calendar
*/
public Calendar getCandidateStartTime(Calendar current) {
if (dtStart.getTime().getTime() > current.getTime().getTime()) {
throw new IllegalArgumentException("Current time before DtStart");
}
int minInterval = getMinimumInterval();
Calendar candidate = (Calendar)current.clone();
if (true) {
// This block is only needed while this function is public...
candidate.clear(Calendar.ZONE_OFFSET);
candidate.clear(Calendar.DST_OFFSET);
candidate.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
candidate.setMinimalDaysInFirstWeek(4);
candidate.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
}
if (frequency == NO_RECURRENCE) {
candidate.setTime(dtStart.getTime());
return candidate;
}
reduce_constant_length_field(Calendar.SECOND, dtStart, candidate);
reduce_constant_length_field(Calendar.MINUTE, dtStart, candidate);
reduce_constant_length_field(Calendar.HOUR_OF_DAY, dtStart, candidate);
switch (minInterval) {
case DAILY :
// No more adjustments needed
break;
case WEEKLY :
reduce_constant_length_field(
Calendar.DAY_OF_WEEK, dtStart, candidate);
break;
case MONTHLY :
reduce_day_of_month(dtStart, candidate);
break;
case YEARLY :
reduce_day_of_year(dtStart, candidate);
break;
}
return candidate;
}
/**
* Method getDtEnd
*
* @return Calendar
*/
public Calendar getDtEnd() {
/*
* Make dtEnd a cloned dtStart, so non-time fields of the Calendar
* are accurate.
*/
Calendar tempEnd = (Calendar)dtStart.clone();
tempEnd.setTime(
new Date(dtStart.getTime().getTime() + duration.getInterval()));
return tempEnd;
}
/**
* Method getDtStart
*
* @return Calendar
*/
public Calendar getDtStart() {
return (Calendar)dtStart.clone();
}
/**
* Method getDuration
*
* @return Duration
*/
public Duration getDuration() {
return (Duration)duration.clone();
}
/**
* Method getFrequency
*
* @return int
*/
public int getFrequency() {
return frequency;
}
/**
* Method getInterval
*
* @return int
*/
public int getInterval() {
return interval;
}
/**
* Method getOccurrence
*
* @return int
*/
public int getOccurrence() {
return occurrence;
}
/**
* Method getUntil
*
* @return Calendar
*/
public Calendar getUntil() {
if (until != null) {
return (Calendar)until.clone();
}
return null;
}
/**
* Method getWeekStart
*
* @return int
*/
public int getWeekStart() {
return dtStart.getFirstDayOfWeek();
}
/**
* Method isInRecurrence
*
* @param current the current time
* @return boolean
*/
public boolean isInRecurrence(Calendar current) {
return isInRecurrence(current, false);
}
/**
* Method isInRecurrence
*
* @param current the current time
* @param debug whether to print debug messages
* @return boolean
*/
public boolean isInRecurrence(Calendar current, boolean debug) {
Calendar myCurrent = (Calendar)current.clone();
// Do all calculations in GMT. Keep other parameters consistent.
myCurrent.clear(Calendar.ZONE_OFFSET);
myCurrent.clear(Calendar.DST_OFFSET);
myCurrent.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
myCurrent.setMinimalDaysInFirstWeek(4);
myCurrent.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
myCurrent.set(Calendar.SECOND, 0);
myCurrent.set(Calendar.MILLISECOND, 0);
if (myCurrent.getTime().getTime() < dtStart.getTime().getTime()) {
// The current time is earlier than the start time.
if (debug) {
System.err.println("current < start");
}
return false;
}
Calendar candidate = getCandidateStartTime(myCurrent);
// Loop over ranges for the duration.
while ((candidate.getTime().getTime() + duration.getInterval()) >
myCurrent.getTime().getTime()) {
if (candidateIsInRecurrence(candidate, debug)) {
return true;
}
// Roll back to one second previous, and try again.
candidate.add(Calendar.SECOND, -1);
// Make sure we haven't rolled back to before dtStart.
if (candidate.getTime().getTime() < dtStart.getTime().getTime()) {
if (debug) {
System.err.println("No candidates after dtStart");
}
return false;
}
candidate = getCandidateStartTime(candidate);
}
if (debug) {
System.err.println("No matching candidates");
}
return false;
}
/**
* Method setByDay
*/
public void setByDay(DayAndPosition[] b) {
if (b == null) {
byDay = null;
return;
}
byDay = new DayAndPosition[b.length];
/*
* System.arraycopy isn't good enough -- we want to clone each
* individual element.
*/
for (int i = 0; i < b.length; i++) {
byDay[i] = (DayAndPosition)b[i].clone();
}
}
/**
* Method setByMonth
*/
public void setByMonth(int[] b) {
if (b == null) {
byMonth = null;
return;
}
byMonth = new int[b.length];
System.arraycopy(b, 0, byMonth, 0, b.length);
}
/**
* Method setByMonthDay
*/
public void setByMonthDay(int[] b) {
if (b == null) {
byMonthDay = null;
return;
}
byMonthDay = new int[b.length];
System.arraycopy(b, 0, byMonthDay, 0, b.length);
}
/**
* Method setByWeekNo
*/
public void setByWeekNo(int[] b) {
if (b == null) {
byWeekNo = null;
return;
}
byWeekNo = new int[b.length];
System.arraycopy(b, 0, byWeekNo, 0, b.length);
}
/**
* Method setByYearDay
*/
public void setByYearDay(int[] b) {
if (b == null) {
byYearDay = null;
return;
}
byYearDay = new int[b.length];
System.arraycopy(b, 0, byYearDay, 0, b.length);
}
/**
* Method setDtEnd
*/
public void setDtEnd(Calendar end) {
Calendar tempEnd = (Calendar)end.clone();
tempEnd.clear(Calendar.ZONE_OFFSET);
tempEnd.clear(Calendar.DST_OFFSET);
tempEnd.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
duration.setInterval(
tempEnd.getTime().getTime() - dtStart.getTime().getTime());
}
/**
* Method setDtStart
*/
public void setDtStart(Calendar start) {
int oldStart = 0;
if (dtStart != null) {
oldStart = dtStart.getFirstDayOfWeek();
}
else {
oldStart = Calendar.MONDAY;
}
if (start == null) {
dtStart = CalendarFactoryUtil.getCalendar(
TimeZoneUtil.getTimeZone(StringPool.UTC));
dtStart.setTime(new Date(0L));
}
else {
dtStart = (Calendar)start.clone();
dtStart.clear(Calendar.ZONE_OFFSET);
dtStart.clear(Calendar.DST_OFFSET);
dtStart.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
}
dtStart.setMinimalDaysInFirstWeek(4);
dtStart.setFirstDayOfWeek(oldStart);
dtStart.set(Calendar.SECOND, 0);
dtStart.set(Calendar.MILLISECOND, 0);
}
/**
* Method setDuration
*/
public void setDuration(Duration d) {
duration = (Duration)d.clone();
}
/**
* Method setFrequency
*/
public void setFrequency(int freq) {
if ((frequency != DAILY) && (frequency != WEEKLY) &&
(frequency != MONTHLY) && (frequency != YEARLY) &&
(frequency != NO_RECURRENCE)) {
throw new IllegalArgumentException("Invalid frequency");
}
frequency = freq;
}
/**
* Method setInterval
*/
public void setInterval(int intr) {
interval = intr;
}
/**
* Method setOccurrence
*/
public void setOccurrence(int occur) {
occurrence = occur;
}
/**
* Method setUntil
*/
public void setUntil(Calendar u) {
if (u == null) {
until = null;
return;
}
until = (Calendar)u.clone();
until.clear(Calendar.ZONE_OFFSET);
until.clear(Calendar.DST_OFFSET);
until.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
}
/**
* Method setWeekStart
*/
public void setWeekStart(int weekstart) {
dtStart.setFirstDayOfWeek(weekstart);
}
/**
* Method toString
*
* @return String
*/
@Override
public String toString() {
StringBundler sb = new StringBundler();
Class<?> clazz = getClass();
sb.append(clazz.getName());
sb.append("[dtStart=");
sb.append((dtStart != null) ? dtStart.toString() : "null");
sb.append(",duration=");
sb.append((duration != null) ? duration.toString() : "null");
sb.append(",frequency=");
sb.append(frequency);
sb.append(",interval=");
sb.append(interval);
sb.append(",until=");
sb.append((until != null) ? until.toString() : "null");
sb.append(",byDay=");
if (byDay == null) {
sb.append("null");
}
else {
sb.append("[");
for (int i = 0; i < byDay.length; i++) {
if (i != 0) {
sb.append(",");
}
if (byDay[i] != null) {
sb.append(byDay[i].toString());
}
else {
sb.append("null");
}
}
sb.append("]");
}
sb.append(",byMonthDay=");
sb.append(stringizeIntArray(byMonthDay));
sb.append(",byYearDay=");
sb.append(stringizeIntArray(byYearDay));
sb.append(",byWeekNo=");
sb.append(stringizeIntArray(byWeekNo));
sb.append(",byMonth=");
sb.append(stringizeIntArray(byMonth));
sb.append(']');
return sb.toString();
}
/**
* Method getDayNumber
*
* @return long
*/
protected static long getDayNumber(Calendar cal) {
Calendar tempCal = (Calendar)cal.clone();
// Set to midnight, GMT
tempCal.set(Calendar.MILLISECOND, 0);
tempCal.set(Calendar.SECOND, 0);
tempCal.set(Calendar.MINUTE, 0);
tempCal.set(Calendar.HOUR_OF_DAY, 0);
return tempCal.getTime().getTime() / (24 * 60 * 60 * 1000);
}
/**
* Method getMonthNumber
*
* @return long
*/
protected static long getMonthNumber(Calendar cal) {
return ((cal.get(Calendar.YEAR) - 1970) * 12L) +
((cal.get(Calendar.MONTH) - Calendar.JANUARY));
}
/**
* Method getWeekNumber
*
* @return long
*/
protected static long getWeekNumber(Calendar cal) {
Calendar tempCal = (Calendar)cal.clone();
// Set to midnight, GMT
tempCal.set(Calendar.MILLISECOND, 0);
tempCal.set(Calendar.SECOND, 0);
tempCal.set(Calendar.MINUTE, 0);
tempCal.set(Calendar.HOUR_OF_DAY, 0);
// Roll back to the first day of the week
int delta =
tempCal.getFirstDayOfWeek() - tempCal.get(Calendar.DAY_OF_WEEK);
if (delta > 0) {
delta -= 7;
}
// tempCal now points to the first instant of this week.
// Calculate the "week epoch" -- the weekstart day closest to January 1,
// 1970 (which was a Thursday)
long weekEpoch =
(tempCal.getFirstDayOfWeek() - Calendar.THURSDAY) * 24L * 60 * 60 *
1000;
return (tempCal.getTime().getTime() - weekEpoch) /
(7 * 24 * 60 * 60 * 1000);
}
/**
* Method reduce_constant_length_field
*/
protected static void reduce_constant_length_field(
int field, Calendar start, Calendar candidate) {
if ((start.getMaximum(field) != start.getLeastMaximum(field)) ||
(start.getMinimum(field) != start.getGreatestMinimum(field))) {
throw new IllegalArgumentException("Not a constant length field");
}
int fieldLength = start.getMaximum(field) - start.getMinimum(field) + 1;
int delta = start.get(field) - candidate.get(field);
if (delta > 0) {
delta -= fieldLength;
}
candidate.add(field, delta);
}
/**
* Method reduce_day_of_month
*/
protected static void reduce_day_of_month(
Calendar start, Calendar candidate) {
Calendar tempCal = (Calendar)candidate.clone();
tempCal.add(Calendar.MONTH, -1);
int delta = start.get(Calendar.DATE) - candidate.get(Calendar.DATE);
if (delta > 0) {
delta -= tempCal.getActualMaximum(Calendar.DATE);
}
candidate.add(Calendar.DATE, delta);
while (start.get(Calendar.DATE) != candidate.get(Calendar.DATE)) {
tempCal.add(Calendar.MONTH, -1);
candidate.add(
Calendar.DATE, -tempCal.getActualMaximum(Calendar.DATE));
}
}
/**
* Method reduce_day_of_year
*/
protected static void reduce_day_of_year(
Calendar start, Calendar candidate) {
if ((start.get(Calendar.MONTH) > candidate.get(Calendar.MONTH)) ||
((start.get(Calendar.MONTH) == candidate.get(Calendar.MONTH)) &&
(start.get(Calendar.DATE) > candidate.get(Calendar.DATE)))) {
candidate.add(Calendar.YEAR, -1);
}
// Set the candidate date to the start date.
candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
candidate.set(Calendar.DATE, start.get(Calendar.DATE));
while ((start.get(Calendar.MONTH) != candidate.get(Calendar.MONTH)) ||
(start.get(Calendar.DATE) != candidate.get(Calendar.DATE))) {
candidate.add(Calendar.YEAR, -1);
candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
candidate.set(Calendar.DATE, start.get(Calendar.DATE));
}
}
/**
* Method candidateIsInRecurrence
*
* @return boolean
*/
protected boolean candidateIsInRecurrence(
Calendar candidate, boolean debug) {
if ((until != null) &&
(candidate.getTime().getTime() > until.getTime().getTime())) {
// After "until"
if (debug) {
System.err.println("after until");
}
return false;
}
if ((getRecurrenceCount(candidate) % interval) != 0) {
// Not a repetition of the interval
if (debug) {
System.err.println("not an interval rep");
}
return false;
}
else if ((occurrence > 0) &&
(getRecurrenceCount(candidate) >= occurrence)) {
return false;
}
if (!matchesByDay(candidate) || !matchesByMonthDay(candidate) ||
!matchesByYearDay(candidate) || !matchesByWeekNo(candidate) ||
!matchesByMonth(candidate)) {
// Doesn't match a by* rule
if (debug) {
System.err.println("doesn't match a by*");
}
return false;
}
if (debug) {
System.err.println("All checks succeeded");
}
return true;
}
/**
* Method getMinimumInterval
*
* @return int
*/
protected int getMinimumInterval() {
if ((frequency == DAILY) || (byDay != null) || (byMonthDay != null) ||
(byYearDay != null)) {
return DAILY;
}
else if ((frequency == WEEKLY) || (byWeekNo != null)) {
return WEEKLY;
}
else if ((frequency == MONTHLY) || (byMonth != null)) {
return MONTHLY;
}
else if (frequency == YEARLY) {
return YEARLY;
}
else if (frequency == NO_RECURRENCE) {
return NO_RECURRENCE;
}
else {
// Shouldn't happen
throw new IllegalStateException(
"Internal error: Unknown frequency value");
}
}
/**
* Method getRecurrenceCount
*
* @return int
*/
protected int getRecurrenceCount(Calendar candidate) {
switch (frequency) {
case NO_RECURRENCE :
return 0;
case DAILY :
return (int)(getDayNumber(candidate) - getDayNumber(dtStart));
case WEEKLY :
Calendar tempCand = (Calendar)candidate.clone();
tempCand.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
return (int)(getWeekNumber(tempCand) - getWeekNumber(dtStart));
case MONTHLY :
return
(int)(getMonthNumber(candidate) - getMonthNumber(dtStart));
case YEARLY :
return
candidate.get(Calendar.YEAR) - dtStart.get(Calendar.YEAR);
default :
throw new IllegalStateException("bad frequency internally...");
}
}
/**
* Method matchesByDay
*
* @return boolean
*/
protected boolean matchesByDay(Calendar candidate) {
if (ArrayUtil.isEmpty(byDay)) {
// No byDay rules, so it matches trivially
return true;
}
for (int i = 0; i < byDay.length; i++) {
if (matchesIndividualByDay(candidate, byDay[i])) {
return true;
}
}
return false;
}
/**
* Method matchesByField
*
* @return boolean
*/
protected boolean matchesByField(
int[] array, int field, Calendar candidate, boolean allowNegative) {
if (ArrayUtil.isEmpty(array)) {
// No rules, so it matches trivially
return true;
}
for (int i = 0; i < array.length; i++) {
int val = 0;
if (allowNegative && (array[i] < 0)) {
// byMonthDay = -1, in a 31-day month, means 31
int max = candidate.getActualMaximum(field);
val = (max + 1) + array[i];
}
else {
val = array[i];
}
if (val == candidate.get(field)) {
return true;
}
}
return false;
}
/**
* Method matchesByMonth
*
* @return boolean
*/
protected boolean matchesByMonth(Calendar candidate) {
return matchesByField(byMonth, Calendar.MONTH, candidate, false);
}
/**
* Method matchesByMonthDay
*
* @return boolean
*/
protected boolean matchesByMonthDay(Calendar candidate) {
return matchesByField(byMonthDay, Calendar.DATE, candidate, true);
}
/**
* Method matchesByWeekNo
*
* @return boolean
*/
protected boolean matchesByWeekNo(Calendar candidate) {
return matchesByField(byWeekNo, Calendar.WEEK_OF_YEAR, candidate, true);
}
/**
* Method matchesByYearDay
*
* @return boolean
*/
protected boolean matchesByYearDay(Calendar candidate) {
return matchesByField(byYearDay, Calendar.DAY_OF_YEAR, candidate, true);
}
/**
* Method matchesIndividualByDay
*
* @return boolean
*/
protected boolean matchesIndividualByDay(
Calendar candidate, DayAndPosition pos) {
if (pos.getDayOfWeek() != candidate.get(Calendar.DAY_OF_WEEK)) {
return false;
}
int position = pos.getDayPosition();
if (position == 0) {
return true;
}
int field = Calendar.DAY_OF_MONTH;
if (position > 0) {
int candidatePosition = ((candidate.get(field) - 1) / 7) + 1;
if (position == candidatePosition) {
return true;
}
return false;
}
else {
// position < 0
int negativeCandidatePosition =
((candidate.getActualMaximum(field) - candidate.get(field)) /
7) + 1;
if (-position == negativeCandidatePosition) {
return true;
}
return false;
}
}
/**
* Method stringizeIntArray
*
* @return String
*/
protected String stringizeIntArray(int[] a) {
if (a == null) {
return "null";
}
StringBundler sb = new StringBundler(2 * a.length + 1);
sb.append("[");
for (int i = 0; i < a.length; i++) {
if (i != 0) {
sb.append(",");
}
sb.append(a[i]);
}
sb.append("]");
return sb.toString();
}
/**
* Field byDay
*/
protected DayAndPosition[] byDay;
/**
* Field byMonth
*/
protected int[] byMonth;
/**
* Field byMonthDay
*/
protected int[] byMonthDay;
/**
* Field byWeekNo
*/
protected int[] byWeekNo;
/**
* Field byYearDay
*/
protected int[] byYearDay;
/**
* Field dtStart
*/
protected Calendar dtStart;
/**
* Field duration
*/
protected Duration duration;
/**
* Field frequency
*/
protected int frequency;
/**
* Field interval
*/
protected int interval;
/**
* Field interval
*/
protected int occurrence;
/**
* Field until
*/
protected Calendar until;
}