// Copyright (C) 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.ical.values;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* encapsulates an RFC 2445 RRULE or EXRULE.
*
* @author mikesamuel+svn@gmail.com (Mike Samuel)
*/
public class RRule extends AbstractIcalObject {
private Frequency freq;
private Weekday wkst;
private DateValue until;
private int count;
private int interval;
private List<WeekdayNum> byDay = new ArrayList<WeekdayNum>();
private int[] byMonth = NO_INTS; // in +/-[1-12]
private int[] byMonthDay = NO_INTS; // in +/-[1-31]
private int[] byWeekNo = NO_INTS; // in +/-[1-53]
private int[] byYearDay = NO_INTS; // in +/-[1-366]
private int[] byHour = NO_INTS; // in [0-23]
private int[] byMinute = NO_INTS; // in [0-59]
private int[] bySecond = NO_INTS; // in [0-60]
private int[] bySetPos = NO_INTS; // in +/-[1-366]
private static final int[] NO_INTS = new int[0];
public RRule() {
this.freq = Frequency.DAILY;
setName("RRULE");
}
public RRule(String icalString) throws ParseException {
parse(VcalRewriter.rewriteRule(icalString), RRuleSchema.instance());
}
/**
* formats as an *unfolded* RFC 2445 content line.
*/
public String toIcal() {
StringBuilder buf = new StringBuilder();
buf.append(this.getName());
if (hasExtParams()) {
for (Map.Entry<String, String> param : getExtParams().entrySet()) {
String k = param.getKey(),
v = param.getValue();
if (ICAL_SPECIALS.matcher(v).find()) {
v = "\"" + v + "\"";
}
buf.append(';').append(k).append('=').append(v);
}
}
buf.append(":FREQ=").append(freq);
if (null != wkst) {
buf.append(";WKST=").append(wkst.toString());
}
if (null != this.until) {
buf.append(";UNTIL=").append(until);
if (until instanceof TimeValue) {
buf.append('Z');
}
}
if (count != 0) {
buf.append(";COUNT=").append(count);
}
if (interval != 0) {
buf.append(";INTERVAL=").append(interval);
}
if (0 != byYearDay.length) {
buf.append(";BYYEARDAY=");
writeIntList(byYearDay, buf);
}
if (0 != byMonth.length) {
buf.append(";BYMONTH=");
writeIntList(byMonth, buf);
}
if (0 != byMonthDay.length) {
buf.append(";BYMONTHDAY=");
writeIntList(byMonthDay, buf);
}
if (0 != byWeekNo.length) {
buf.append(";BYWEEKNO=");
writeIntList(byWeekNo, buf);
}
if (!byDay.isEmpty()) {
buf.append(";BYDAY=");
boolean first = true;
for (WeekdayNum day : this.byDay) {
if (!first) {
buf.append(',');
} else {
first = false;
}
buf.append(day);
}
}
if (0 != byHour.length) {
buf.append(";BYHOUR=");
writeIntList(byHour, buf);
}
if (0 != byMinute.length) {
buf.append(";BYMINUTE=");
writeIntList(byMinute, buf);
}
if (0 != bySecond.length) {
buf.append(";BYSECOND=");
writeIntList(bySecond, buf);
}
if (0 != bySetPos.length) {
buf.append(";BYSETPOS=");
writeIntList(bySetPos, buf);
}
return buf.toString();
}
private static void writeIntList(int[] nums, StringBuilder out) {
for (int i = 0; i < nums.length; ++i) {
if (0 != i) { out.append(','); }
out.append(nums[i]);
}
}
/** an approximate number of days between occurences. */
public int approximateIntervalInDays() {
int freqLengthDays;
int nPerPeriod = 0;
switch (this.freq) {
case DAILY:
freqLengthDays = 1;
break;
case WEEKLY:
freqLengthDays = 7;
if (!this.byDay.isEmpty()) {
nPerPeriod = this.byDay.size();
}
break;
case MONTHLY:
freqLengthDays = 30;
if (!this.byDay.isEmpty()) {
for (WeekdayNum day : byDay) {
// if it's every weekday in the month, assume four of that weekday,
// otherwise there is one of that week-in-month,weekday pair
nPerPeriod += 0 != day.num ? 1 : 4;
}
} else {
nPerPeriod = this.byMonthDay.length;
}
break;
case YEARLY:
freqLengthDays = 365;
int monthCount = 12;
if (0 != this.byMonth.length) {
monthCount = this.byMonth.length;
}
if (!this.byDay.isEmpty()) {
for (WeekdayNum day : byDay) {
// if it's every weekend in the months in the year,
// assume 4 of that weekday per month,
// otherwise there is one of that week-in-month,weekday pair per
// month
nPerPeriod += (0 != day.num ? 1 : 4) * monthCount;
}
} else if (0 != this.byMonthDay.length) {
nPerPeriod += monthCount * this.byMonthDay.length;
} else {
nPerPeriod += this.byYearDay.length;
}
break;
default: freqLengthDays = 0;
}
if (0 == nPerPeriod) { nPerPeriod = 1; }
return ((freqLengthDays / nPerPeriod) * this.interval);
}
/** the frequency of repetition */
public Frequency getFreq() { return this.freq; }
public void setFreq(Frequency freq) {
this.freq = freq;
}
/** day of the week the week starts on */
public Weekday getWkSt() { return this.wkst; }
public void setWkSt(Weekday wkst) {
this.wkst = wkst;
}
public DateValue getUntil() { return this.until; }
public void setUntil(DateValue until) {
this.until = until;
}
public int getCount() { return this.count; }
public void setCount(int count) {
this.count = count;
}
public int getInterval() { return this.interval; }
public void setInterval(int interval) {
this.interval = interval;
}
public List<WeekdayNum> getByDay() { return this.byDay; }
public void setByDay(List<WeekdayNum> byDay) {
this.byDay = new ArrayList<WeekdayNum>(byDay);
}
public int[] getByMonth() { return this.byMonth; }
public void setByMonth(int[] byMonth) {
this.byMonth = byMonth.clone();
}
public int[] getByMonthDay() { return this.byMonthDay; }
public void setByMonthDay(int[] byMonthDay) {
this.byMonthDay = byMonthDay.clone();
}
public int[] getByWeekNo() { return this.byWeekNo; }
public void setByWeekNo(int[] byWeekNo) {
this.byWeekNo = byWeekNo.clone();
}
public int[] getByYearDay() { return this.byYearDay; }
public void setByYearDay(int[] byYearDay) {
this.byYearDay = byYearDay.clone();
}
public int[] getBySetPos() { return this.bySetPos; }
public void setBySetPos(int[] bySetPos) {
this.bySetPos = bySetPos.clone();
}
public int[] getByHour() { return this.byHour; }
public void setByHour(int[] byHour) {
this.byHour = byHour.clone();
}
public int[] getByMinute() { return this.byMinute; }
public void setByMinute(int[] byMinute) {
this.byMinute = byMinute.clone();
}
public int[] getBySecond() { return this.bySecond; }
public void setBySecond(int[] bySecond) {
this.bySecond = bySecond.clone();
}
}