1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.scheduler.api;
23
24 import static net.fortuna.ical4j.model.parameter.Value.DATE_TIME;
25
26 import net.fortuna.ical4j.model.DateList;
27 import net.fortuna.ical4j.model.DateTime;
28 import net.fortuna.ical4j.model.Period;
29 import net.fortuna.ical4j.model.Recur;
30 import net.fortuna.ical4j.model.TimeZoneRegistry;
31 import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
32 import net.fortuna.ical4j.model.property.RRule;
33
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 import java.text.SimpleDateFormat;
38 import java.time.ZoneOffset;
39 import java.time.ZonedDateTime;
40 import java.util.Calendar;
41 import java.util.Date;
42 import java.util.LinkedList;
43 import java.util.List;
44 import java.util.TimeZone;
45
46 public final class Util {
47
48 public static final int EVENT_MINIMUM_SEPARATION_MILLISECONDS = 0;
49
50 private static final Logger logger = LoggerFactory.getLogger(Util.class);
51 private static final TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry();
52
53 private Util() {
54 }
55
56
57
58
59
60
61
62
63
64
65 public static void adjustRrule(final RRule rRule, final Date start, final TimeZone tz) {
66 final Recur recur = rRule.getRecur();
67 if (recur.getHourList().size() != 1 || recur.getMinuteList().size() != 1) {
68 throw new IllegalArgumentException(
69 "RRules with multiple hours/minutes are not supported by Opencast. " + recur.toString());
70 }
71 final ZonedDateTime adjustedDate = ZonedDateTime.ofInstant(start.toInstant(), ZoneOffset.UTC)
72 .withHour(recur.getHourList().get(0))
73 .withMinute(recur.getMinuteList().get(0))
74 .withZoneSameInstant(tz.toZoneId());
75 recur.getHourList().set(0, adjustedDate.getHour());
76 recur.getMinuteList().set(0, adjustedDate.getMinute());
77 }
78
79
80
81
82
83
84
85
86
87
88
89 public static List<Period> calculatePeriods(Date start, Date end, long duration, RRule rRule, TimeZone tz) {
90 Calendar startCal = Calendar.getInstance(tz);
91 Calendar endCal = Calendar.getInstance(tz);
92 startCal.setTime(start);
93 endCal.setTime(end);
94 return Util.calculatePeriods(startCal, endCal, duration, rRule.getRecur(), tz);
95 }
96
97
98
99
100
101
102
103
104
105
106
107
108 public static List<Period> calculatePeriods(
109 Calendar startCalTz, Calendar endCalTz, long duration, Recur recur, TimeZone tz) {
110 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EE MMM dd HH:mm:ss zzz yyyy");
111 simpleDateFormat.setTimeZone(tz);
112 String tzStr = tz.getID();
113 List<Period> event = new LinkedList<>();
114 logger.debug("Inbound start of recurrence {} to end of recurrence {}, in Tz {}",
115 simpleDateFormat.format(startCalTz.getTime()),
116 simpleDateFormat.format(endCalTz.getTime()), tzStr);
117
118 DateTime periodStart = new DateTime(startCalTz.getTime());
119 logger.debug("ical4j timeZone for {} is {}", tzStr, registry.getTimeZone(tzStr).toZoneId());
120 periodStart.setTimeZone(registry.getTimeZone(tzStr));
121 DateTime periodEnd = new DateTime(endCalTz.getTime());
122 periodEnd.setTimeZone(registry.getTimeZone(tzStr));
123
124 logger.trace("is utc {}? Tz is {} ", periodStart.isUtc(), periodStart.getTimeZone().toZoneId());
125 logger.debug("({}) Looking at recurrences for {} to {}, duration {}, {}", periodStart.getTimeZone().toZoneId(),
126 simpleDateFormat.format(new Date(periodStart.getTime())),
127 simpleDateFormat.format(new Date(periodEnd.getTime())), duration, recur.toString());
128
129 DateList dates = recur.getDates(periodStart, periodEnd, DATE_TIME);
130 logger.trace("Got {} dates: {}, tz '{}'", dates.size(), dates.toString(), dates.getTimeZone().toZoneId());
131
132 for (Date date : dates) {
133 Date endTZ = new DateTime(date.getTime() + duration);
134 DateTime startDT = new DateTime(date);
135 DateTime endDT = new DateTime(endTZ);
136 Period p = new Period(startDT, endDT);
137 event.add(p);
138 }
139 for (Period e: event) {
140 Calendar cal = Calendar.getInstance(e.getStart().getTimeZone());
141 cal.setTimeInMillis(e.getStart().getTime());
142 logger.debug("EventList start {} Instance {}, calendar hour {}, zone {}",
143 e.getStart().toString(),
144 simpleDateFormat.format(cal.getTime()),
145 cal.get(Calendar.HOUR_OF_DAY),
146 e.getStart().getTimeZone().toZoneId());
147 }
148 return event;
149 }
150
151
152
153
154
155
156
157
158
159 public static boolean schedulingIntervalsOverlap(
160 final Date start1, final Date end1, final Date start2, final Date end2) {
161 return end2.after(start1) && end1.after(start2)
162 || eventWithinMinimumSeparation(start1, end1, start2, end2);
163 }
164
165
166
167
168
169 private static boolean eventWithinMinimumSeparation(Date checkStart, Date checkEnd, Date start, Date end) {
170 return Math.abs(checkStart.getTime() - start.getTime()) < EVENT_MINIMUM_SEPARATION_MILLISECONDS
171 || Math.abs(checkStart.getTime() - end.getTime()) < EVENT_MINIMUM_SEPARATION_MILLISECONDS
172 || Math.abs(checkEnd.getTime() - start.getTime()) < EVENT_MINIMUM_SEPARATION_MILLISECONDS
173 || Math.abs(checkEnd.getTime() - end.getTime()) < EVENT_MINIMUM_SEPARATION_MILLISECONDS;
174 }
175 }