42 import java.util.Map; |
42 import java.util.Map; |
43 import java.util.NoSuchElementException; |
43 import java.util.NoSuchElementException; |
44 import java.util.StringTokenizer; |
44 import java.util.StringTokenizer; |
45 |
45 |
46 import net.fortuna.ical4j.model.parameter.Value; |
46 import net.fortuna.ical4j.model.parameter.Value; |
|
47 import net.fortuna.ical4j.util.CompatibilityHints; |
47 import net.fortuna.ical4j.util.Configurator; |
48 import net.fortuna.ical4j.util.Configurator; |
48 import net.fortuna.ical4j.util.Dates; |
49 import net.fortuna.ical4j.util.Dates; |
49 |
50 |
50 import org.apache.commons.logging.Log; |
51 import org.apache.commons.logging.Log; |
51 import org.apache.commons.logging.LogFactory; |
52 import org.apache.commons.logging.LogFactory; |
169 private NumberList monthList; |
170 private NumberList monthList; |
170 |
171 |
171 private NumberList setPosList; |
172 private NumberList setPosList; |
172 |
173 |
173 private String weekStartDay; |
174 private String weekStartDay; |
|
175 |
|
176 private int calendarWeekStartDay; |
174 |
177 |
175 private Map experimentalValues = new HashMap(); |
178 private Map experimentalValues = new HashMap(); |
176 |
179 |
177 // Calendar field we increment based on frequency. |
180 // Calendar field we increment based on frequency. |
178 private int calIncField; |
181 private int calIncField; |
179 |
182 |
180 /** |
183 /** |
181 * Default constructor. |
184 * Default constructor. |
182 */ |
185 */ |
183 public Recur() { |
186 public Recur() { |
|
187 // default week start is Monday per RFC5545 |
|
188 calendarWeekStartDay = Calendar.MONDAY; |
184 } |
189 } |
185 |
190 |
186 /** |
191 /** |
187 * Constructs a new instance from the specified string value. |
192 * Constructs a new instance from the specified string value. |
188 * @param aValue a string representation of a recurrence. |
193 * @param aValue a string representation of a recurrence. |
189 * @throws ParseException thrown when the specified string contains an invalid representation of an UNTIL date value |
194 * @throws ParseException thrown when the specified string contains an invalid representation of an UNTIL date value |
190 */ |
195 */ |
191 public Recur(final String aValue) throws ParseException { |
196 public Recur(final String aValue) throws ParseException { |
|
197 // default week start is Monday per RFC5545 |
|
198 calendarWeekStartDay = Calendar.MONDAY; |
192 final StringTokenizer t = new StringTokenizer(aValue, ";="); |
199 final StringTokenizer t = new StringTokenizer(aValue, ";="); |
193 while (t.hasMoreTokens()) { |
200 while (t.hasMoreTokens()) { |
194 final String token = t.nextToken(); |
201 final String token = t.nextToken(); |
195 if (FREQ.equals(token)) { |
202 if (FREQ.equals(token)) { |
196 frequency = nextToken(t, token); |
203 frequency = nextToken(t, token); |
239 else if (BYSETPOS.equals(token)) { |
246 else if (BYSETPOS.equals(token)) { |
240 setPosList = new NumberList(nextToken(t, token), 1, 366, true); |
247 setPosList = new NumberList(nextToken(t, token), 1, 366, true); |
241 } |
248 } |
242 else if (WKST.equals(token)) { |
249 else if (WKST.equals(token)) { |
243 weekStartDay = nextToken(t, token); |
250 weekStartDay = nextToken(t, token); |
244 } |
251 calendarWeekStartDay = WeekDay.getCalendarDay(new WeekDay(weekStartDay)); |
245 // assume experimental value.. |
252 } |
246 else { |
253 else { |
247 experimentalValues.put(token, nextToken(t, token)); |
254 if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING)) { |
|
255 // assume experimental value.. |
|
256 experimentalValues.put(token, nextToken(t, token)); |
|
257 } |
|
258 else { |
|
259 throw new IllegalArgumentException("Invalid recurrence rule part: " + |
|
260 token + "=" + nextToken(t, token)); |
|
261 } |
248 } |
262 } |
249 } |
263 } |
250 validateFrequency(); |
264 validateFrequency(); |
251 } |
265 } |
252 |
266 |
262 /** |
276 /** |
263 * @param frequency a recurrence frequency string |
277 * @param frequency a recurrence frequency string |
264 * @param until maximum recurrence date |
278 * @param until maximum recurrence date |
265 */ |
279 */ |
266 public Recur(final String frequency, final Date until) { |
280 public Recur(final String frequency, final Date until) { |
|
281 // default week start is Monday per RFC5545 |
|
282 calendarWeekStartDay = Calendar.MONDAY; |
267 this.frequency = frequency; |
283 this.frequency = frequency; |
268 this.until = until; |
284 this.until = until; |
269 validateFrequency(); |
285 validateFrequency(); |
270 } |
286 } |
271 |
287 |
272 /** |
288 /** |
273 * @param frequency a recurrence frequency string |
289 * @param frequency a recurrence frequency string |
274 * @param count maximum recurrence count |
290 * @param count maximum recurrence count |
275 */ |
291 */ |
276 public Recur(final String frequency, final int count) { |
292 public Recur(final String frequency, final int count) { |
|
293 // default week start is Monday per RFC5545 |
|
294 calendarWeekStartDay = Calendar.MONDAY; |
277 this.frequency = frequency; |
295 this.frequency = frequency; |
278 this.count = count; |
296 this.count = count; |
279 validateFrequency(); |
297 validateFrequency(); |
280 } |
298 } |
281 |
299 |
576 } |
597 } |
577 else { |
598 else { |
578 dates.setTimeZone(((DateTime) seed).getTimeZone()); |
599 dates.setTimeZone(((DateTime) seed).getTimeZone()); |
579 } |
600 } |
580 } |
601 } |
581 final Calendar cal = Dates.getCalendarInstance(seed); |
602 final Calendar cal = getCalendarInstance(seed, true); |
582 cal.setTime(seed); |
|
583 |
603 |
584 // optimize the start time for selecting candidates |
604 // optimize the start time for selecting candidates |
585 // (only applicable where a COUNT is not specified) |
605 // (only applicable where a COUNT is not specified) |
586 if (getCount() < 1) { |
606 if (getCount() < 1) { |
587 final Calendar seededCal = (Calendar) cal.clone(); |
607 final Calendar seededCal = (Calendar) cal.clone(); |
668 * @param seed the start date of this Recurrence's first instance |
688 * @param seed the start date of this Recurrence's first instance |
669 * @param startDate the date to start the search |
689 * @param startDate the date to start the search |
670 */ |
690 */ |
671 public final Date getNextDate(final Date seed, final Date startDate) { |
691 public final Date getNextDate(final Date seed, final Date startDate) { |
672 |
692 |
673 final Calendar cal = Dates.getCalendarInstance(seed); |
693 final Calendar cal = getCalendarInstance(seed, true); |
674 cal.setTime(seed); |
|
675 |
694 |
676 // optimize the start time for selecting candidates |
695 // optimize the start time for selecting candidates |
677 // (only applicable where a COUNT is not specified) |
696 // (only applicable where a COUNT is not specified) |
678 if (getCount() < 1) { |
697 if (getCount() < 1) { |
679 final Calendar seededCal = (Calendar) cal.clone(); |
698 final Calendar seededCal = (Calendar) cal.clone(); |
855 return dates; |
874 return dates; |
856 } |
875 } |
857 final DateList monthlyDates = getDateListInstance(dates); |
876 final DateList monthlyDates = getDateListInstance(dates); |
858 for (final Iterator i = dates.iterator(); i.hasNext();) { |
877 for (final Iterator i = dates.iterator(); i.hasNext();) { |
859 final Date date = (Date) i.next(); |
878 final Date date = (Date) i.next(); |
860 final Calendar cal = Dates.getCalendarInstance(date); |
879 final Calendar cal = getCalendarInstance(date, true); |
861 cal.setTime(date); |
880 |
862 for (final Iterator j = getMonthList().iterator(); j.hasNext();) { |
881 for (final Iterator j = getMonthList().iterator(); j.hasNext();) { |
863 final Integer month = (Integer) j.next(); |
882 final Integer month = (Integer) j.next(); |
864 // Java months are zero-based.. |
883 // Java months are zero-based.. |
865 // cal.set(Calendar.MONTH, month.intValue() - 1); |
884 // cal.set(Calendar.MONTH, month.intValue() - 1); |
866 cal.roll(Calendar.MONTH, (month.intValue() - 1) - cal.get(Calendar.MONTH)); |
885 cal.roll(Calendar.MONTH, (month.intValue() - 1) - cal.get(Calendar.MONTH)); |
881 return dates; |
900 return dates; |
882 } |
901 } |
883 final DateList weekNoDates = getDateListInstance(dates); |
902 final DateList weekNoDates = getDateListInstance(dates); |
884 for (final Iterator i = dates.iterator(); i.hasNext();) { |
903 for (final Iterator i = dates.iterator(); i.hasNext();) { |
885 final Date date = (Date) i.next(); |
904 final Date date = (Date) i.next(); |
886 final Calendar cal = Dates.getCalendarInstance(date); |
905 final Calendar cal = getCalendarInstance(date, true); |
887 cal.setTime(date); |
|
888 for (final Iterator j = getWeekNoList().iterator(); j.hasNext();) { |
906 for (final Iterator j = getWeekNoList().iterator(); j.hasNext();) { |
889 final Integer weekNo = (Integer) j.next(); |
907 final Integer weekNo = (Integer) j.next(); |
890 cal.set(Calendar.WEEK_OF_YEAR, Dates.getAbsWeekNo(cal.getTime(), weekNo.intValue())); |
908 cal.set(Calendar.WEEK_OF_YEAR, Dates.getAbsWeekNo(cal.getTime(), weekNo.intValue())); |
891 weekNoDates.add(Dates.getInstance(cal.getTime(), weekNoDates.getType())); |
909 weekNoDates.add(Dates.getInstance(cal.getTime(), weekNoDates.getType())); |
892 } |
910 } |
905 return dates; |
923 return dates; |
906 } |
924 } |
907 final DateList yearDayDates = getDateListInstance(dates); |
925 final DateList yearDayDates = getDateListInstance(dates); |
908 for (final Iterator i = dates.iterator(); i.hasNext();) { |
926 for (final Iterator i = dates.iterator(); i.hasNext();) { |
909 final Date date = (Date) i.next(); |
927 final Date date = (Date) i.next(); |
910 final Calendar cal = Dates.getCalendarInstance(date); |
928 final Calendar cal = getCalendarInstance(date, true); |
911 cal.setTime(date); |
|
912 for (final Iterator j = getYearDayList().iterator(); j.hasNext();) { |
929 for (final Iterator j = getYearDayList().iterator(); j.hasNext();) { |
913 final Integer yearDay = (Integer) j.next(); |
930 final Integer yearDay = (Integer) j.next(); |
914 cal.set(Calendar.DAY_OF_YEAR, Dates.getAbsYearDay(cal.getTime(), yearDay.intValue())); |
931 cal.set(Calendar.DAY_OF_YEAR, Dates.getAbsYearDay(cal.getTime(), yearDay.intValue())); |
915 yearDayDates.add(Dates.getInstance(cal.getTime(), yearDayDates.getType())); |
932 yearDayDates.add(Dates.getInstance(cal.getTime(), yearDayDates.getType())); |
916 } |
933 } |
929 return dates; |
946 return dates; |
930 } |
947 } |
931 final DateList monthDayDates = getDateListInstance(dates); |
948 final DateList monthDayDates = getDateListInstance(dates); |
932 for (final Iterator i = dates.iterator(); i.hasNext();) { |
949 for (final Iterator i = dates.iterator(); i.hasNext();) { |
933 final Date date = (Date) i.next(); |
950 final Date date = (Date) i.next(); |
934 final Calendar cal = Dates.getCalendarInstance(date); |
951 final Calendar cal = getCalendarInstance(date, false); |
935 cal.setLenient(false); |
|
936 cal.setTime(date); |
|
937 for (final Iterator j = getMonthDayList().iterator(); j.hasNext();) { |
952 for (final Iterator j = getMonthDayList().iterator(); j.hasNext();) { |
938 final Integer monthDay = (Integer) j.next(); |
953 final Integer monthDay = (Integer) j.next(); |
939 try { |
954 try { |
940 cal.set(Calendar.DAY_OF_MONTH, Dates.getAbsMonthDay(cal.getTime(), monthDay.intValue())); |
955 cal.set(Calendar.DAY_OF_MONTH, Dates.getAbsMonthDay(cal.getTime(), monthDay.intValue())); |
941 monthDayDates.add(Dates.getInstance(cal.getTime(), monthDayDates.getType())); |
956 monthDayDates.add(Dates.getInstance(cal.getTime(), monthDayDates.getType())); |
967 for (final Iterator j = getDayList().iterator(); j.hasNext();) { |
982 for (final Iterator j = getDayList().iterator(); j.hasNext();) { |
968 final WeekDay weekDay = (WeekDay) j.next(); |
983 final WeekDay weekDay = (WeekDay) j.next(); |
969 // if BYYEARDAY or BYMONTHDAY is specified filter existing |
984 // if BYYEARDAY or BYMONTHDAY is specified filter existing |
970 // list.. |
985 // list.. |
971 if (!getYearDayList().isEmpty() || !getMonthDayList().isEmpty()) { |
986 if (!getYearDayList().isEmpty() || !getMonthDayList().isEmpty()) { |
972 final Calendar cal = Dates.getCalendarInstance(date); |
987 final Calendar cal = getCalendarInstance(date, true); |
973 cal.setTime(date); |
|
974 if (weekDay.equals(WeekDay.getWeekDay(cal))) { |
988 if (weekDay.equals(WeekDay.getWeekDay(cal))) { |
975 weekDayDates.add(date); |
989 weekDayDates.add(date); |
976 } |
990 } |
977 } |
991 } |
978 else { |
992 else { |
989 * @param date |
1003 * @param date |
990 * @param weekDay |
1004 * @param weekDay |
991 * @return |
1005 * @return |
992 */ |
1006 */ |
993 private List getAbsWeekDays(final Date date, final Value type, final WeekDay weekDay) { |
1007 private List getAbsWeekDays(final Date date, final Value type, final WeekDay weekDay) { |
994 final Calendar cal = Dates.getCalendarInstance(date); |
1008 final Calendar cal = getCalendarInstance(date, true); |
995 // default week start is Monday per RFC5545 |
|
996 int calendarWeekStartDay = Calendar.MONDAY; |
|
997 if (weekStartDay != null) { |
|
998 calendarWeekStartDay = WeekDay.getCalendarDay(new WeekDay(weekStartDay)); |
|
999 } |
|
1000 cal.setFirstDayOfWeek(calendarWeekStartDay); |
|
1001 cal.setTime(date); |
|
1002 |
|
1003 final DateList days = new DateList(type); |
1009 final DateList days = new DateList(type); |
1004 if (date instanceof DateTime) { |
1010 if (date instanceof DateTime) { |
1005 if (((DateTime) date).isUtc()) { |
1011 if (((DateTime) date).isUtc()) { |
1006 days.setUtc(true); |
1012 days.setUtc(true); |
1007 } |
1013 } |
1093 return dates; |
1099 return dates; |
1094 } |
1100 } |
1095 final DateList hourlyDates = getDateListInstance(dates); |
1101 final DateList hourlyDates = getDateListInstance(dates); |
1096 for (final Iterator i = dates.iterator(); i.hasNext();) { |
1102 for (final Iterator i = dates.iterator(); i.hasNext();) { |
1097 final Date date = (Date) i.next(); |
1103 final Date date = (Date) i.next(); |
1098 final Calendar cal = Dates.getCalendarInstance(date); |
1104 final Calendar cal = getCalendarInstance(date, true); |
1099 cal.setTime(date); |
|
1100 for (final Iterator j = getHourList().iterator(); j.hasNext();) { |
1105 for (final Iterator j = getHourList().iterator(); j.hasNext();) { |
1101 final Integer hour = (Integer) j.next(); |
1106 final Integer hour = (Integer) j.next(); |
1102 cal.set(Calendar.HOUR_OF_DAY, hour.intValue()); |
1107 cal.set(Calendar.HOUR_OF_DAY, hour.intValue()); |
1103 hourlyDates.add(Dates.getInstance(cal.getTime(), hourlyDates.getType())); |
1108 hourlyDates.add(Dates.getInstance(cal.getTime(), hourlyDates.getType())); |
1104 } |
1109 } |
1117 return dates; |
1122 return dates; |
1118 } |
1123 } |
1119 final DateList minutelyDates = getDateListInstance(dates); |
1124 final DateList minutelyDates = getDateListInstance(dates); |
1120 for (final Iterator i = dates.iterator(); i.hasNext();) { |
1125 for (final Iterator i = dates.iterator(); i.hasNext();) { |
1121 final Date date = (Date) i.next(); |
1126 final Date date = (Date) i.next(); |
1122 final Calendar cal = Dates.getCalendarInstance(date); |
1127 final Calendar cal = getCalendarInstance(date, true); |
1123 cal.setTime(date); |
|
1124 for (final Iterator j = getMinuteList().iterator(); j.hasNext();) { |
1128 for (final Iterator j = getMinuteList().iterator(); j.hasNext();) { |
1125 final Integer minute = (Integer) j.next(); |
1129 final Integer minute = (Integer) j.next(); |
1126 cal.set(Calendar.MINUTE, minute.intValue()); |
1130 cal.set(Calendar.MINUTE, minute.intValue()); |
1127 minutelyDates.add(Dates.getInstance(cal.getTime(), minutelyDates.getType())); |
1131 minutelyDates.add(Dates.getInstance(cal.getTime(), minutelyDates.getType())); |
1128 } |
1132 } |
1141 return dates; |
1145 return dates; |
1142 } |
1146 } |
1143 final DateList secondlyDates = getDateListInstance(dates); |
1147 final DateList secondlyDates = getDateListInstance(dates); |
1144 for (final Iterator i = dates.iterator(); i.hasNext();) { |
1148 for (final Iterator i = dates.iterator(); i.hasNext();) { |
1145 final Date date = (Date) i.next(); |
1149 final Date date = (Date) i.next(); |
1146 final Calendar cal = Dates.getCalendarInstance(date); |
1150 final Calendar cal = getCalendarInstance(date, true); |
1147 cal.setTime(date); |
|
1148 for (final Iterator j = getSecondList().iterator(); j.hasNext();) { |
1151 for (final Iterator j = getSecondList().iterator(); j.hasNext();) { |
1149 final Integer second = (Integer) j.next(); |
1152 final Integer second = (Integer) j.next(); |
1150 cal.set(Calendar.SECOND, second.intValue()); |
1153 cal.set(Calendar.SECOND, second.intValue()); |
1151 secondlyDates.add(Dates.getInstance(cal.getTime(), secondlyDates.getType())); |
1154 secondlyDates.add(Dates.getInstance(cal.getTime(), secondlyDates.getType())); |
1152 } |
1155 } |
1216 this.until = until; |
1219 this.until = until; |
1217 this.count = -1; |
1220 this.count = -1; |
1218 } |
1221 } |
1219 |
1222 |
1220 /** |
1223 /** |
|
1224 * Construct a Calendar object and sets the time. |
|
1225 * @param date |
|
1226 * @param lenient |
|
1227 * @return |
|
1228 */ |
|
1229 private Calendar getCalendarInstance(final Date date, final boolean lenient) { |
|
1230 Calendar cal = Dates.getCalendarInstance(date); |
|
1231 // A week should have at least 4 days to be considered as such per RFC5545 |
|
1232 cal.setMinimalDaysInFirstWeek(4); |
|
1233 cal.setFirstDayOfWeek(calendarWeekStartDay); |
|
1234 cal.setLenient(lenient); |
|
1235 cal.setTime(date); |
|
1236 |
|
1237 return cal; |
|
1238 } |
|
1239 |
|
1240 /** |
1221 * @param stream |
1241 * @param stream |
1222 * @throws IOException |
1242 * @throws IOException |
1223 * @throws ClassNotFoundException |
1243 * @throws ClassNotFoundException |
1224 */ |
1244 */ |
1225 private void readObject(final java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { |
1245 private void readObject(final java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { |
1231 * Instantiate a new datelist with the same type, timezone and utc settings |
1251 * Instantiate a new datelist with the same type, timezone and utc settings |
1232 * as the origList. |
1252 * as the origList. |
1233 * @param origList |
1253 * @param origList |
1234 * @return a new empty list. |
1254 * @return a new empty list. |
1235 */ |
1255 */ |
1236 private static final DateList getDateListInstance(final DateList origList) { |
1256 private static DateList getDateListInstance(final DateList origList) { |
1237 final DateList list = new DateList(origList.getType()); |
1257 final DateList list = new DateList(origList.getType()); |
1238 if (origList.isUtc()) { |
1258 if (origList.isUtc()) { |
1239 list.setUtc(true); |
1259 list.setUtc(true); |
1240 } else { |
1260 } else { |
1241 list.setTimeZone(origList.getTimeZone()); |
1261 list.setTimeZone(origList.getTimeZone()); |