Tue, 10 Feb 2015 18:12:00 +0100
Import initial revisions of existing project AndroidCaldavSyncAdapater,
forked from upstream repository at 27e8a0f8495c92e0780d450bdf0c7cec77a03a55.
1 /**
2 * Copyright (c) 2012, Ben Fortuna
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * o Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * o Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * o Neither the name of Ben Fortuna nor the names of any other contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32 package net.fortuna.ical4j.model;
34 import java.text.ParseException;
35 import java.util.Date;
37 import org.apache.commons.lang.builder.EqualsBuilder;
38 import org.apache.commons.lang.builder.HashCodeBuilder;
40 /**
41 * $Id$ [Apr 14, 2004]
42 *
43 * Defines a period of time. A period may be specified as either a start date
44 * and end date, or a start date and duration. NOTE: End dates and durations are
45 * implicitly derived when not explicitly specified. This means that you cannot
46 * rely on the returned values from the getters to deduce whether a period has
47 * an explicit end date or duration.
48 *
49 * @author Ben Fortuna
50 */
51 public class Period extends DateRange implements Comparable {
53 private static final long serialVersionUID = 7321090422911676490L;
55 private Dur duration;
57 /**
58 * Constructor.
59 *
60 * @param aValue
61 * a string representation of a period
62 * @throws ParseException
63 * where the specified string is not a valid representation
64 */
65 public Period(final String aValue) throws ParseException {
66 super(parseStartDate(aValue), parseEndDate(aValue, true));
68 // period may end in either a date-time or a duration..
69 try {
70 parseEndDate(aValue, false);
71 }
72 catch (ParseException pe) {
73 // duration = DurationFormat.getInstance().parse(aValue);
74 duration = parseDuration(aValue);
75 }
76 normalise();
77 }
79 /**
80 * Constructs a new period with the specied start and end date.
81 *
82 * @param start
83 * the start date of the period
84 * @param end
85 * the end date of the period
86 */
87 public Period(final DateTime start, final DateTime end) {
88 super(start, end);
89 normalise();
90 }
92 /**
93 * Constructs a new period with the specified start date and duration.
94 *
95 * @param start
96 * the start date of the period
97 * @param duration
98 * the duration of the period
99 */
100 public Period(final DateTime start, final Dur duration) {
101 super(start, new DateTime(duration.getTime(start)));
102 this.duration = duration;
103 normalise();
104 }
106 private static DateTime parseStartDate(String value) throws ParseException {
107 return new DateTime(value.substring(0, value.indexOf('/')));
108 }
110 private static DateTime parseEndDate(String value, boolean resolve) throws ParseException {
111 DateTime end = null;
112 try {
113 end = new DateTime(value.substring(value.indexOf('/') + 1));
114 }
115 catch (ParseException e) {
116 if (resolve) {
117 final Dur duration = parseDuration(value);
118 end = new DateTime(duration.getTime(parseStartDate(value)));
119 }
120 else {
121 throw e;
122 }
123 }
124 return end;
125 }
127 private static Dur parseDuration(String value) {
128 return new Dur(value.substring(value.indexOf('/') + 1));
129 }
131 private void normalise() {
132 // ensure the end timezone is the same as the start..
133 if (getStart().isUtc()) {
134 getEnd().setUtc(true);
135 }
136 else {
137 getEnd().setTimeZone(getStart().getTimeZone());
138 }
139 }
141 /**
142 * Returns the duration of this period. If an explicit duration is not
143 * specified, the duration is derived from the end date.
144 *
145 * @return the duration of this period in milliseconds.
146 */
147 public final Dur getDuration() {
148 if (duration == null) {
149 return new Dur(getStart(), getEnd());
150 }
151 return duration;
152 }
154 /**
155 * Returns the end date of this period. If an explicit end date is not
156 * specified, the end date is derived from the duration.
157 *
158 * @return the end date of this period.
159 */
160 public final DateTime getEnd() {
161 return (DateTime) getRangeEnd();
162 }
164 /**
165 * @return Returns the start.
166 */
167 public final DateTime getStart() {
168 return (DateTime) getRangeStart();
169 }
171 /**
172 * @param date a date to test for inclusion
173 * @param inclusive indicates if the start and end of the period are included in the test
174 * @return true if the specified date occurs within the current period
175 * @deprecated use {@link Period#includes(Date, int)} instead.
176 */
177 public final boolean includes(final Date date, final boolean inclusive) {
178 if (inclusive) {
179 return includes(date, INCLUSIVE_START | INCLUSIVE_END);
180 }
181 else {
182 return includes(date, 0);
183 }
184 }
186 /**
187 * Creates a period that encompasses both this period and another one. If
188 * the other period is null, return a copy of this period. NOTE: Resulting
189 * periods are specified by explicitly setting a start date and end date
190 * (i.e. durations are implied).
191 *
192 * @param period
193 * the period to add to this one
194 * @return a period
195 */
196 public final Period add(final Period period) {
197 DateTime newPeriodStart = null;
198 DateTime newPeriodEnd = null;
200 if (period == null) {
201 newPeriodStart = getStart();
202 newPeriodEnd = getEnd();
203 }
204 else {
205 if (getStart().before(period.getStart())) {
206 newPeriodStart = getStart();
207 }
208 else {
209 newPeriodStart = period.getStart();
210 }
211 if (getEnd().after(period.getEnd())) {
212 newPeriodEnd = getEnd();
213 }
214 else {
215 newPeriodEnd = period.getEnd();
216 }
217 }
219 return new Period(newPeriodStart, newPeriodEnd);
220 }
222 /**
223 * Creates a set of periods resulting from the subtraction of the specified
224 * period from this one. If the specified period is completely contained
225 * in this period, the resulting list will contain two periods. Otherwise
226 * it will contain one. If the specified period does not interest this period
227 * a list containing this period is returned. If this period is completely
228 * contained within the specified period an empty period list is returned.
229 * @param period a period to subtract from this one
230 * @return a list containing zero, one or two periods.
231 */
232 public final PeriodList subtract(final Period period) {
233 final PeriodList result = new PeriodList();
235 if (period.contains(this)) {
236 return result;
237 }
238 else if (!period.intersects(this)) {
239 result.add(this);
240 return result;
241 }
243 DateTime newPeriodStart;
244 DateTime newPeriodEnd;
245 if (!period.getStart().after(getStart())) {
246 newPeriodStart = period.getEnd();
247 newPeriodEnd = getEnd();
248 }
249 else if (!period.getEnd().before(getEnd())) {
250 newPeriodStart = getStart();
251 newPeriodEnd = period.getStart();
252 }
253 else {
254 // subtraction consumed by this period..
255 // initialise and add head period..
256 newPeriodStart = getStart();
257 newPeriodEnd = period.getStart();
258 result.add(new Period(newPeriodStart, newPeriodEnd));
259 // initialise tail period..
260 newPeriodStart = period.getEnd();
261 newPeriodEnd = getEnd();
262 }
263 result.add(new Period(newPeriodStart, newPeriodEnd));
264 return result;
265 }
267 /**
268 * An empty period is one that consumes no time.
269 * @return true if this period consumes no time, otherwise false
270 */
271 public final boolean isEmpty() {
272 return getStart().equals(getEnd());
273 }
275 /**
276 * Updates the start and (possible) end times of this period to reflect
277 * the specified UTC timezone status.
278 * @param utc indicates whether the period is in UTC time
279 */
280 public void setUtc(final boolean utc) {
281 getStart().setUtc(utc);
282 getEnd().setUtc(utc);
283 }
285 /**
286 * Updates the start and (possible) end times of this period to reflect
287 * the specified timezone status.
288 * @param timezone a timezone for the period
289 */
290 public final void setTimeZone(final TimeZone timezone) {
291 getStart().setUtc(false);
292 getStart().setTimeZone(timezone);
293 getEnd().setUtc(false);
294 getEnd().setTimeZone(timezone);
295 }
297 /**
298 * {@inheritDoc}
299 */
300 public final String toString() {
301 final StringBuffer b = new StringBuffer();
302 b.append(getStart());
303 b.append('/');
304 if (duration == null) {
305 b.append(getEnd());
306 }
307 else {
308 // b.append(DurationFormat.getInstance().format(duration));
309 b.append(duration);
310 }
311 return b.toString();
312 }
314 /**
315 * {@inheritDoc}
316 */
317 public final int compareTo(final Object arg0) {
318 return compareTo((Period) arg0);
319 }
321 /**
322 * Compares the specified period with this period.
323 *
324 * @param arg0 a period to compare with this one
325 * @return a postive value if this period is greater, negative if the other is
326 * greater, or zero if they are equal
327 */
328 public final int compareTo(final Period arg0) {
329 // Throws documented exception if type is wrong or parameter is null
330 if (arg0 == null) {
331 throw new ClassCastException("Cannot compare this object to null");
332 }
333 final int startCompare = getStart().compareTo(arg0.getStart());
334 if (startCompare != 0) {
335 return startCompare;
336 }
337 // start dates are equal, compare end dates..
338 else if (duration == null) {
339 final int endCompare = getEnd().compareTo(arg0.getEnd());
340 if (endCompare != 0) {
341 return endCompare;
342 }
343 }
344 // ..or durations
345 return getDuration().compareTo(arg0.getDuration());
346 }
348 /**
349 * {@inheritDoc}
350 */
351 public final boolean equals(final Object o) {
352 if (this == o) {
353 return true;
354 }
355 if (!(o instanceof Period)) {
356 return false;
357 }
359 final Period period = (Period) o;
360 return new EqualsBuilder().append(getStart(), period.getStart())
361 .append(getEnd(), period.getEnd()).isEquals();
362 }
364 /**
365 * {@inheritDoc}
366 */
367 public final int hashCode() {
368 return new HashCodeBuilder().append(getStart())
369 .append((duration == null) ? (Object) getEnd() : duration).toHashCode();
370 }
371 }