/* * Created on 14/02/2005 * * $Id: RecurTest.java,v 1.16 2006/06/17 02:00:17 fortuna Exp $ * * Copyright (c) 2005, Ben Fortuna * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * o Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * o Neither the name of Ben Fortuna nor the names of any other contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package net.fortuna.ical4j.model; import java.text.ParseException; import java.util.Arrays; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.TimeZone; import junit.framework.TestCase; import net.fortuna.ical4j.model.parameter.Value; import net.fortuna.ical4j.model.property.DtEnd; import net.fortuna.ical4j.model.property.DtStart; import net.fortuna.ical4j.util.TimeZones; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * @author Ben Fortuna */ public class RecurTest extends TestCase { private static Log log = LogFactory.getLog(RecurTest.class); private TimeZone originalDefault; /* (non-Javadoc) * @see junit.framework.TestCase#setUp() */ protected void setUp() throws Exception { originalDefault = TimeZone.getDefault(); } /* (non-Javadoc) * @see junit.framework.TestCase#tearDown() */ protected void tearDown() throws Exception { TimeZone.setDefault(originalDefault); } /** * */ public void testGetDates() { Recur recur = new Recur(Recur.DAILY, 10); recur.setInterval(2); log.info(recur); Calendar cal = Calendar.getInstance(); Date start = new Date(cal.getTime().getTime()); cal.add(Calendar.DAY_OF_WEEK_IN_MONTH, 10); Date end = new Date(cal.getTime().getTime()); log.info(recur.getDates(start, end, Value.DATE_TIME)); recur.setUntil(new Date(cal.getTime().getTime())); log.info(recur); log.info(recur.getDates(start, end, Value.DATE_TIME)); recur.setFrequency(Recur.WEEKLY); recur.getDayList().add(WeekDay.MO); log.info(recur); DateList dates = recur.getDates(start, end, Value.DATE); log.info(dates); assertTrue("Date list exceeds COUNT limit", dates.size() <= 10); } /** * Test BYDAY rules. */ public void testGetDatesByDay() { Recur recur = new Recur(Recur.DAILY, 10); recur.setInterval(1); recur.getDayList().add(WeekDay.MO); recur.getDayList().add(WeekDay.TU); recur.getDayList().add(WeekDay.WE); recur.getDayList().add(WeekDay.TH); recur.getDayList().add(WeekDay.FR); log.info(recur); Calendar cal = Calendar.getInstance(); Date start = new Date(cal.getTime().getTime()); cal.add(Calendar.DAY_OF_WEEK_IN_MONTH, 10); Date end = new Date(cal.getTime().getTime()); DateList dates = recur.getDates(start, end, Value.DATE_TIME); log.info(dates); assertTrue("Date list exceeds COUNT limit", dates.size() <= 10); } /** * Test BYDAY recurrence rules.. */ public void testGetDatesByDay2() throws ParseException { String rrule = "FREQ=MONTHLY;WKST=SU;INTERVAL=2;BYDAY=5TU"; Recur recur = new Recur(rrule); Calendar cal = Calendar.getInstance(); cal.clear(Calendar.SECOND); java.util.Date start = cal.getTime(); cal.add(Calendar.YEAR, 2); java.util.Date end = cal.getTime(); DateList recurrences = recur.getDates(new Date(start), new Date(end), Value.DATE); for (Iterator i = recurrences.iterator(); i.hasNext();) { Date recurrence = (Date) i.next(); cal.setTime(recurrence); assertEquals(5, cal.get(Calendar.WEEK_OF_MONTH)); } } /** * */ public void testGetDatesWithBase() { /* * Here is an example of evaluating multiple BYxxx rule parts. * * DTSTART;TZID=US-Eastern:19970105T083000 * RRULE:FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9; * BYMINUTE=30 */ Calendar testCal = Calendar.getInstance(); testCal.set(Calendar.YEAR, 1997); testCal.set(Calendar.MONTH, 1); testCal.set(Calendar.DAY_OF_MONTH, 5); testCal.set(Calendar.HOUR, 8); testCal.set(Calendar.MINUTE, 30); testCal.set(Calendar.SECOND, 0); Recur recur = new Recur(Recur.YEARLY, -1); recur.setInterval(2); recur.getMonthList().add(new Integer(1)); recur.getDayList().add(WeekDay.SU); recur.getHourList().add(new Integer(8)); recur.getHourList().add(new Integer(9)); recur.getMinuteList().add(new Integer(30)); Calendar cal = Calendar.getInstance(); Date start = new DateTime(cal.getTime().getTime()); cal.add(Calendar.YEAR, 2); Date end = new DateTime(cal.getTime().getTime()); log.info(recur); DateList dates = recur.getDates(new DateTime(testCal.getTime()), start, end, Value.DATE_TIME); log.info(dates); } /* public void testSublistNegative() { List list = new LinkedList(); list.add("1"); list.add("2"); list.add("3"); assertSublistEquals(list, list, 0); assertSublistEquals(asList("3"), list, -1); assertSublistEquals(asList("2"), list, -2); assertSublistEquals(asList("1"), list, -3); assertSublistEquals(list, list, -4); } public void testSublistPositive() { List list = new LinkedList(); list.add("1"); list.add("2"); list.add("3"); assertSublistEquals(list, list, 0); assertSublistEquals(asList("1"), list, 1); assertSublistEquals(asList("2"), list, 2); assertSublistEquals(asList("3"), list, 3); assertSublistEquals(list, list, 4); } private void assertSublistEquals(List expected, List list, int offset) { List sublist = new LinkedList(); Recur.sublist(list, offset, sublist); assertEquals(expected, sublist); } private List asList(Object o) { List list = new LinkedList(); list.add(o); return list; } public void testSetPosNegative() throws Exception { Date[] dates = new Date[] { new Date(1), new Date(2), new Date(3) }; Date[] expected = new Date[] { new Date(3), new Date(2) }; assertSetPosApplied(expected, dates, "BYSETPOS=-1,-2"); } public void testSetPosPositve() throws Exception { Date[] dates = new Date[] { new Date(1), new Date(2), new Date(3) }; Date[] expected = new Date[] { new Date(2), new Date(3) }; assertSetPosApplied(expected, dates, "BYSETPOS=2,3"); } public void testSetPosOutOfBounds() throws Exception { Date[] dates = new Date[] { new Date(1) }; Date[] expected = new Date[] {}; assertSetPosApplied(expected, dates, "BYSETPOS=-2,2"); } private void assertSetPosApplied(Date[] expected, Date[] dates, String rule) throws Exception { Recur recur = new Recur(rule); DateList expectedList = asDateList(expected); assertEquals(expectedList, recur.applySetPosRules(asDateList(dates))); } */ private DateList asDateList(Date[] dates) { DateList dateList = new DateList(Value.DATE); dateList.addAll(Arrays.asList(dates)); return dateList; } /** * This test creates a rule outside of the specified boundaries to * confirm that the returned date list is empty. * <pre> * Weekly on Tuesday and Thursday for 5 weeks: * * DTSTART;TZID=US-Eastern:19970902T090000 * RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH * or * * RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH * * ==> (1997 9:00 AM EDT)September 2,4,9,11,16,18,23,25,30;October 2 * </pre> */ public final void testBoundaryProcessing() { Recur recur = new Recur(Recur.WEEKLY, 10); recur.getDayList().add(WeekDay.TU); recur.getDayList().add(WeekDay.TH); log.info(recur); Calendar cal = Calendar.getInstance(); cal.set(Calendar.YEAR, 1997); cal.set(Calendar.MONTH, Calendar.SEPTEMBER); cal.set(Calendar.DAY_OF_MONTH, 2); cal.set(Calendar.HOUR_OF_DAY, 9); cal.clear(Calendar.MINUTE); cal.clear(Calendar.SECOND); Date seed = new DateTime(cal.getTime()); cal = Calendar.getInstance(); Date start = new DateTime(cal.getTime()); cal.add(Calendar.YEAR, 2); Date end = new DateTime(cal.getTime()); DateList dates = recur.getDates(seed, start, end, Value.DATE_TIME); log.info(dates); assertTrue(dates.isEmpty()); } /** * This test confirms SETPOS rules are working correctly. * <pre> * The BYSETPOS rule part specifies a COMMA character (US-ASCII decimal * 44) separated list of values which corresponds to the nth occurrence * within the set of events specified by the rule. Valid values are 1 to * 366 or -366 to -1. It MUST only be used in conjunction with another * BYxxx rule part. For example "the last work day of the month" could * be represented as: * * RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1 * </pre> */ public final void testSetPosProcessing() { Recur recur = new Recur(Recur.MONTHLY, -1); recur.getDayList().add(WeekDay.MO); recur.getDayList().add(WeekDay.TU); recur.getDayList().add(WeekDay.WE); recur.getDayList().add(WeekDay.TH); recur.getDayList().add(WeekDay.FR); recur.getSetPosList().add(new Integer(-1)); log.info(recur); Calendar cal = Calendar.getInstance(); Date start = new DateTime(cal.getTime()); cal.add(Calendar.YEAR, 2); Date end = new DateTime(cal.getTime()); DateList dates = recur.getDates(start, end, Value.DATE_TIME); log.info(dates); } public void testMgmill2001() { Calendar cal = Calendar.getInstance(); cal.set(Calendar.DAY_OF_MONTH, 11); cal.set(Calendar.MONTH, 0); cal.set(Calendar.YEAR, 2005); java.util.Date eventStart = cal.getTime(); cal.set(Calendar.DAY_OF_MONTH, 1); java.util.Date rangeStart = cal.getTime(); cal.set(Calendar.YEAR, 2009); java.util.Date rangeEnd = cal.getTime(); // FREQ=MONTHLY;INTERVAL=1;COUNT=4;BYMONTHDAY=2 Recur recur = new Recur(Recur.MONTHLY, 4); recur.setInterval(1); recur.getMonthDayList().add(new Integer(2)); getDates(rangeStart, rangeEnd, eventStart, recur); // FREQ=MONTHLY;INTERVAL=2;COUNT=4;BYDAY=2MO recur = new Recur(Recur.MONTHLY, 4); recur.setInterval(2); recur.getDayList().add(new WeekDay(WeekDay.MO, 2)); getDates(rangeStart, rangeEnd, eventStart, recur); // FREQ=YEARLY;COUNT=4;BYMONTH=2;BYMONTHDAY=3 recur = new Recur(Recur.YEARLY, 4); recur.getMonthList().add(new Integer(2)); recur.getMonthDayList().add(new Integer(3)); getDates(rangeStart, rangeEnd, eventStart, recur); // FREQ=YEARLY;COUNT=4;BYMONTH=2;BYDAY=2SU recur = new Recur(Recur.YEARLY, 4); recur.getMonthList().add(new Integer(2)); recur.getDayList().add(new WeekDay(WeekDay.SU, 2)); getDates(rangeStart, rangeEnd, eventStart, recur); } private void getDates(java.util.Date startRange, java.util.Date endRange, java.util.Date eventStart, Recur recur) { net.fortuna.ical4j.model.Date start = new net.fortuna.ical4j.model.Date(startRange); net.fortuna.ical4j.model.Date end = new net.fortuna.ical4j.model.Date(endRange); net.fortuna.ical4j.model.Date seed = new net.fortuna.ical4j.model.Date(eventStart); DateList dates = recur.getDates(seed, start, end, Value.DATE); for (int i=0; i<dates.size(); i++) { System.out.println("date_" + i + " = " + dates.get(i).toString()); } } /** * @throws ParseException */ public final void testRecurGetDates() throws ParseException { Recur recur = new Recur("FREQ=WEEKLY;INTERVAL=1;BYDAY=SA"); Date start = new Date("20050101Z"); Date end = new Date("20060101Z"); DateList list = recur.getDates(start, end, null); for (int i = 0; i < list.size(); i++) { Date date = (Date) list.get(i); // Calendar calendar = Dates.getCalendarInstance(date); Calendar calendar = Calendar.getInstance(); //TimeZone.getTimeZone("Etc/UTC")); calendar.setTime(date); assertEquals(Calendar.SATURDAY, calendar.get(Calendar.DAY_OF_WEEK)); } } public void testGetDatesRalph() throws ParseException { Recur recur = new Recur("FREQ=WEEKLY;WKST=MO;INTERVAL=1;UNTIL=20051003T000000Z;BYDAY=MO,WE"); Calendar queryStartDate = new GregorianCalendar(TimeZone.getTimeZone(TimeZones.UTC_ID)); queryStartDate.set(2005, Calendar.SEPTEMBER, 3, 0, 0, 0); Calendar queryEndDate = new GregorianCalendar(TimeZone.getTimeZone(TimeZones.UTC_ID)); queryEndDate.set(2005, Calendar.OCTOBER, 31, 23, 59, 0); DateList dateList = recur.getDates(new DateTime(queryStartDate.getTime()), new DateTime(queryStartDate.getTime()), new DateTime(queryEndDate.getTime()), Value.DATE_TIME); log.info(dateList); } /** * Test ordering of returned dates. * @throws ParseException */ public void testDateOrdering() throws ParseException { String s1 = "FREQ=WEEKLY;COUNT=75;INTERVAL=2;BYDAY=SU,MO,TU;WKST=SU"; Recur rec = new Recur(s1); Date d1 = new Date(); Calendar cal = Calendar.getInstance(); cal.add(Calendar.YEAR,1); Date d2 = new Date(cal.getTimeInMillis()); DateList dl1 = rec.getDates(d1,d2, Value.DATE_TIME); Date prev = null; Date event = null; for(int i=0; i<dl1.size(); i++) { prev = event; event = (Date) dl1.get(i); log.info("Occurence "+i+" at "+event); assertTrue(prev == null || !prev.after(event)); } } /** * @throws ParseException */ public void testMonthByDay() throws ParseException { String rrule = "FREQ=MONTHLY;UNTIL=20061220T000000;INTERVAL=1;BYDAY=3WE"; Recur recur = new Recur(rrule); Calendar cal = Calendar.getInstance(); Date start = new Date(cal.getTime()); cal.add(Calendar.YEAR, 1); DateList recurrences = recur.getDates(start, new Date(cal.getTime()), Value.DATE); } /** * @throws ParseException */ public void testAlternateTimeZone() throws ParseException { String rrule = "FREQ=WEEKLY;BYDAY=WE;BYHOUR=12;BYMINUTE=0"; Recur recur = new Recur(rrule); TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); TimeZoneRegistry tzreg = TimeZoneRegistryFactory.getInstance().createRegistry(); Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")); cal.clear(Calendar.SECOND); java.util.Date start = cal.getTime(); DtStart dtStart = new DtStart(new DateTime(start)); dtStart.setTimeZone(tzreg.getTimeZone("America/Los_Angeles")); cal.add(Calendar.MONTH, 2); java.util.Date end = cal.getTime(); DtEnd dtEnd = new DtEnd(new DateTime(end)); DateList recurrences = recur.getDates(dtStart.getDate(), dtEnd.getDate(), Value.DATE_TIME); for (Iterator i = recurrences.iterator(); i.hasNext();) { DateTime recurrence = (DateTime) i.next(); assertEquals(tzreg.getTimeZone("America/Los_Angeles"), recurrence.getTimeZone()); } } /** * @throws ParseException */ public void testFriday13Recur() throws ParseException { String rrule = "FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13"; Recur recur = new Recur(rrule); Calendar cal = Calendar.getInstance(); cal.clear(Calendar.SECOND); cal.set(1997, 0, 1); java.util.Date start = cal.getTime(); cal.set(2000, 0, 1); java.util.Date end = cal.getTime(); DateList recurrences = recur.getDates(new Date(start), new Date(end), Value.DATE); for (Iterator i = recurrences.iterator(); i.hasNext();) { Date recurrence = (Date) i.next(); cal.setTime(recurrence); assertEquals(13, cal.get(Calendar.DAY_OF_MONTH)); assertEquals(Calendar.FRIDAY, cal.get(Calendar.DAY_OF_WEEK)); } } public void testNoFrequency() throws ParseException { String rrule = "BYDAY=MO,TU,WE,TH,FR"; try { new Recur(rrule); fail("IllegalArgumentException not thrown!"); } catch (IllegalArgumentException e) { // expected } } public void testUnknownFrequency() throws ParseException { String rrule = "FREQ=FORTNIGHTLY;BYDAY=MO,TU,WE,TH,FR"; try { new Recur(rrule); fail("IllegalArgumentException not thrown!"); } catch (IllegalArgumentException e) { // expected } } }